Resolve "Richtig schönes, cooles Siegerpodest." #273
@@ -128,6 +128,15 @@ class GameConsumer(AsyncWebsocketConsumer):
|
|||||||
host_id = text_data_json['host_id']
|
host_id = text_data_json['host_id']
|
||||||
if await self.verify_host(host_id):
|
if await self.verify_host(host_id):
|
||||||
await self.advance_to_next_question()
|
await self.advance_to_next_question()
|
||||||
|
elif message_type == 'winner_podest':
|
||||||
|
host_id = text_data_json['host_id']
|
||||||
|
if await self.verify_host(host_id):
|
||||||
|
await self.show_winner_podest()
|
||||||
|
|
||||||
|
elif message_type == 'scoreboard':
|
||||||
|
host_id = text_data_json['host_id']
|
||||||
|
if await self.verify_host(host_id):
|
||||||
|
await self.show_scoreboard()
|
||||||
|
|
||||||
elif message_type == 'finish_game':
|
elif message_type == 'finish_game':
|
||||||
host_id = text_data_json['host_id']
|
host_id = text_data_json['host_id']
|
||||||
@@ -430,6 +439,26 @@ class GameConsumer(AsyncWebsocketConsumer):
|
|||||||
'redirect_url': reverse('play:question', kwargs={'join_code': self.join_code})
|
'redirect_url': reverse('play:question', kwargs={'join_code': self.join_code})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
elif result == 'winner_podest':
|
||||||
|
# Notify clients to redirect to next question
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
self.game_group_name,
|
||||||
|
{
|
||||||
|
'type': 'game_state_update',
|
||||||
|
'action': 'show_winner_podest',
|
||||||
|
'redirect_url': reverse('play:winner_podest', kwargs={'join_code': self.join_code})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
elif result == 'scoreboard':
|
||||||
|
# Notify clients to redirect to next question
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
self.game_group_name,
|
||||||
|
{
|
||||||
|
'type': 'game_state_update',
|
||||||
|
'action': 'show_scoreboard',
|
||||||
|
'redirect_url': reverse('play:scoreboard', kwargs={'join_code': self.join_code})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
def _show_scores(self):
|
def _show_scores(self):
|
||||||
@@ -476,3 +505,48 @@ class GameConsumer(AsyncWebsocketConsumer):
|
|||||||
'redirect_url': reverse('play:finished', kwargs={'join_code': self.join_code})
|
'redirect_url': reverse('play:finished', kwargs={'join_code': self.join_code})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@database_sync_to_async
|
||||||
|
def _show_winner_podest(self):
|
||||||
|
try:
|
||||||
|
quiz_game = QuizGame.objects.get(join_code=self.join_code)
|
||||||
|
quiz_game.current_state = 'winner_podest'
|
||||||
|
quiz_game.save()
|
||||||
|
return True
|
||||||
|
except QuizGame.DoesNotExist:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def show_winner_podest(self):
|
||||||
|
success = await self._show_winner_podest()
|
||||||
|
if success:
|
||||||
|
# Notify clients to redirect to finished page
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
self.game_group_name,
|
||||||
|
{
|
||||||
|
'type': 'game_state_update',
|
||||||
|
'action': 'show_winner_podest',
|
||||||
|
'redirect_url': reverse('play:winner_podest', kwargs={'join_code': self.join_code})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@database_sync_to_async
|
||||||
|
def _show_scoreboard(self):
|
||||||
|
try:
|
||||||
|
quiz_game = QuizGame.objects.get(join_code=self.join_code)
|
||||||
|
quiz_game.current_state = 'scoreboard'
|
||||||
|
quiz_game.save()
|
||||||
|
return True
|
||||||
|
except QuizGame.DoesNotExist:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def show_scoreboard(self):
|
||||||
|
success = await self._show_scoreboard()
|
||||||
|
if success:
|
||||||
|
# Notify clients to redirect to finished page
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
self.game_group_name,
|
||||||
|
{
|
||||||
|
'type': 'game_state_update',
|
||||||
|
'action': 'show_scoreboard',
|
||||||
|
'redirect_url': reverse('play:scoreboard', kwargs={'join_code': self.join_code})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
18
django/play/migrations/0014_alter_quizgame_current_state.py
Normal file
18
django/play/migrations/0014_alter_quizgame_current_state.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.7 on 2025-07-25 16:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('play', '0013_quizanswer_answer_text_quizanswer_answers_order_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='quizgame',
|
||||||
|
name='current_state',
|
||||||
|
field=models.CharField(choices=[('lobby', 'In Lobby'), ('question', 'Frage läuft'), ('scores', 'Punkteübersicht'), ('winner_podest', 'Siegerpodest'), ('finished', 'Beendet')], default='lobby', max_length=20),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
django/play/migrations/0015_alter_quizgame_current_state.py
Normal file
18
django/play/migrations/0015_alter_quizgame_current_state.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.7 on 2025-07-28 13:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('play', '0014_alter_quizgame_current_state'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='quizgame',
|
||||||
|
name='current_state',
|
||||||
|
field=models.CharField(choices=[('lobby', 'In Lobby'), ('question', 'Frage läuft'), ('scores', 'Punkteübersicht'), ('winner_podest', 'Siegerpodest'), ('finished', 'Beendet'), ('scoreboard', 'Scoreboard')], default='lobby', max_length=20),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -10,7 +10,10 @@ class QuizGame(models.Model):
|
|||||||
('lobby', 'In Lobby'),
|
('lobby', 'In Lobby'),
|
||||||
('question', 'Frage läuft'),
|
('question', 'Frage läuft'),
|
||||||
('scores', 'Punkteübersicht'),
|
('scores', 'Punkteübersicht'),
|
||||||
('finished', 'Beendet')
|
('winner_podest','Siegerpodest'),
|
||||||
|
('finished', 'Beendet'),
|
||||||
|
('scoreboard', 'Scoreboard'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
host_id = models.CharField(max_length=200, unique=True)
|
host_id = models.CharField(max_length=200, unique=True)
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ urlpatterns = [
|
|||||||
path('game/<str:join_code>/waiting', views.waiting_room, name='waiting'),
|
path('game/<str:join_code>/waiting', views.waiting_room, name='waiting'),
|
||||||
path('game/<str:join_code>/scores', views.scores, name='scores'),
|
path('game/<str:join_code>/scores', views.scores, name='scores'),
|
||||||
path('game/<str:join_code>/finished', views.finished, name='finished'),
|
path('game/<str:join_code>/finished', views.finished, name='finished'),
|
||||||
|
path('game/<str:join_code>/winner_podest', views.winner_podest, name='winner_podest'),
|
||||||
|
path('game/<str:join_code>/scoreboard', views.scoreboard, name='scoreboard'),
|
||||||
path('select_mode/<str:join_code>', views.select_mode, name='select_mode'),
|
path('select_mode/<str:join_code>', views.select_mode, name='select_mode'),
|
||||||
path('selected_mode/<str:join_code>', views.selected_mode, name='selected_mode'),
|
path('selected_mode/<str:join_code>', views.selected_mode, name='selected_mode'),
|
||||||
]
|
]
|
||||||
@@ -188,6 +188,49 @@ def scores(request, join_code):
|
|||||||
'question_index': quiz_game.current_question_index,
|
'question_index': quiz_game.current_question_index,
|
||||||
'total_questions': len(questions)
|
'total_questions': len(questions)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def scoreboard(request, join_code):
|
||||||
|
quiz_game = get_object_or_404(QuizGame, join_code=join_code)
|
||||||
|
|
||||||
|
# Verify game state
|
||||||
|
if quiz_game.current_state != 'scoreboard':
|
||||||
|
return redirect('play:lobby', join_code=join_code)
|
||||||
|
|
||||||
|
# Get participants sorted by score
|
||||||
|
participants = QuizGameParticipant.objects.filter(quiz_game=quiz_game).order_by('-score')
|
||||||
|
|
||||||
|
# Check if user is host
|
||||||
|
is_host = False
|
||||||
|
if 'host_id' in request.COOKIES and request.COOKIES['host_id'] == quiz_game.host_id:
|
||||||
|
is_host = True
|
||||||
|
|
||||||
|
# Get current question for context
|
||||||
|
questions = quiz_game.quiz_id.questions.all()
|
||||||
|
current_question = questions[quiz_game.current_question_index]
|
||||||
|
question_data = json.loads(current_question.data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
my_participant = request.session.get('my_participant-participant_id')
|
||||||
|
participant = QuizGameParticipant.objects.get(participant_id=my_participant)
|
||||||
|
my_participant=participant
|
||||||
|
except:
|
||||||
|
participant = QuizGameParticipant.objects.none()
|
||||||
|
|
||||||
|
return render(request, 'play/game/scoreboard.html', {
|
||||||
|
'quiz_game': quiz_game,
|
||||||
|
'participants': participants,
|
||||||
|
'participant': participant,
|
||||||
|
'my_participant':my_participant,
|
||||||
|
'is_host': is_host,
|
||||||
|
'host_id': quiz_game.host_id if is_host else None,
|
||||||
|
'is_last_question': quiz_game.current_question_index + 1 >= len(questions),
|
||||||
|
'question_data': question_data,
|
||||||
|
'question_index': quiz_game.current_question_index,
|
||||||
|
'total_questions': len(questions)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def finished(request, join_code):
|
def finished(request, join_code):
|
||||||
|
|
||||||
@@ -225,6 +268,45 @@ def finished(request, join_code):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def winner_podest(request, join_code):
|
||||||
|
quiz_game = get_object_or_404(QuizGame, join_code=join_code)
|
||||||
|
|
||||||
|
# Verify game state
|
||||||
|
if quiz_game.current_state != 'winner_podest':
|
||||||
|
return redirect('play:lobby', join_code=join_code)
|
||||||
|
|
||||||
|
# Get participants sorted by score
|
||||||
|
participants = QuizGameParticipant.objects.filter(quiz_game=quiz_game).order_by('-score')
|
||||||
|
|
||||||
|
# Check if user is host
|
||||||
|
is_host = False
|
||||||
|
if 'host_id' in request.COOKIES and request.COOKIES['host_id'] == quiz_game.host_id:
|
||||||
|
is_host = True
|
||||||
|
|
||||||
|
# Get current question for context
|
||||||
|
questions = quiz_game.quiz_id.questions.all()
|
||||||
|
current_question = questions[quiz_game.current_question_index]
|
||||||
|
question_data = json.loads(current_question.data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
my_participant = request.session.get('my_participant-participant_id')
|
||||||
|
participant = QuizGameParticipant.objects.get(participant_id=my_participant)
|
||||||
|
my_participant=participant
|
||||||
|
except:
|
||||||
|
participant = QuizGameParticipant.objects.none()
|
||||||
|
|
||||||
|
return render(request, 'play/game/winner_podest.html', {
|
||||||
|
'quiz_game': quiz_game,
|
||||||
|
'participants': participants,
|
||||||
|
'participant': participant,
|
||||||
|
'my_participant':my_participant,
|
||||||
|
'is_host': is_host,
|
||||||
|
'host_id': quiz_game.host_id if is_host else None,
|
||||||
|
'is_last_question': quiz_game.current_question_index + 1 >= len(questions),
|
||||||
|
'question_data': question_data,
|
||||||
|
'question_index': quiz_game.current_question_index,
|
||||||
|
'total_questions': len(questions)
|
||||||
|
})
|
||||||
|
|
||||||
def finished(request, join_code):
|
def finished(request, join_code):
|
||||||
quiz_game = get_object_or_404(QuizGame, join_code=join_code)
|
quiz_game = get_object_or_404(QuizGame, join_code=join_code)
|
||||||
@@ -262,7 +344,6 @@ def finished(request, join_code):
|
|||||||
return redirect('home')
|
return redirect('home')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def question(request, join_code):
|
def question(request, join_code):
|
||||||
quiz_game = get_object_or_404(QuizGame, join_code=join_code)
|
quiz_game = get_object_or_404(QuizGame, join_code=join_code)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="grid place-content-center gap-2 w-full mt-4">
|
<div class="grid place-content-center gap-2 w-full mt-4">
|
||||||
<div class="flex space-x-2 pb-4">
|
<div class="bottom-0 left-0 w-full p-2 flex justify-center space-x-4 z-50">
|
||||||
<p class="text-sm font-extralight dark:text-white"><a href="/impress">Impressum</a></p>
|
<p class="text-sm font-extralight dark:text-white"><a href="/impress">Impressum</a></p>
|
||||||
<p class="text-sm font-extralight dark:text-white"><a href="/privacy">Datenschutz</a></p>
|
<p class="text-sm font-extralight dark:text-white"><a href="/privacy">Datenschutz</a></p>
|
||||||
<p class="text-sm font-extralight dark:text-white"><a href="https://edugit.org/enrichment-2024/qivip">Repository</a></p>
|
<p class="text-sm font-extralight dark:text-white"><a href="https://edugit.org/enrichment-2024/qivip">Repository</a></p>
|
||||||
|
|||||||
@@ -153,7 +153,7 @@
|
|||||||
{% if request.session.mode == "learn" %}
|
{% if request.session.mode == "learn" %}
|
||||||
<form action="">
|
<form action="">
|
||||||
<input id="textanswer" type="text" placeholder="DEINE ANTWORT"
|
<input id="textanswer" type="text" placeholder="DEINE ANTWORT"
|
||||||
class="answer-option text-center p-2 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option lg:w-80 dark:bg-transparent dark:text-white dark:hover:bg-[#2a2f3a] ">
|
class="answer-option text-center mb-4 p-2 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option lg:w-80 dark:bg-transparent dark:text-white dark:hover:bg-[#2a2f3a] ">
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onclick="submitAnswer( -1 );"
|
onclick="submitAnswer( -1 );"
|
||||||
|
|||||||
@@ -160,7 +160,7 @@
|
|||||||
{% elif question_data.type == "input" %}
|
{% elif question_data.type == "input" %}
|
||||||
<form action="">
|
<form action="">
|
||||||
<input id="textanswer" type="text" placeholder="DEINE ANTWORT"
|
<input id="textanswer" type="text" placeholder="DEINE ANTWORT"
|
||||||
class="answer-option text-center p-2 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option lg:w-80 dark:bg-transparent dark:text-white dark:hover:bg-[#2a2f3a]">
|
class="answer-option text-center p-2 mb-4 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option lg:w-80 dark:bg-transparent dark:text-white dark:hover:bg-[#2a2f3a]">
|
||||||
<button
|
<button
|
||||||
onclick="submitAnswer( -1 );"
|
onclick="submitAnswer( -1 );"
|
||||||
class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg transition-colors duration-200 answer-option "
|
class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg transition-colors duration-200 answer-option "
|
||||||
@@ -318,6 +318,7 @@ function updateOrderNumbers() {
|
|||||||
|
|
||||||
|
|
||||||
function submitOrderAnswer() {
|
function submitOrderAnswer() {
|
||||||
|
if (hasAnswered) return;
|
||||||
const order_button=document.getElementById("send_order")
|
const order_button=document.getElementById("send_order")
|
||||||
order_button.disabled=true;
|
order_button.disabled=true;
|
||||||
order_button.classList.add('opacity-50');
|
order_button.classList.add('opacity-50');
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6 dark:bg-transparent dark:text-white">
|
<div class="bg-white rounded-lg shadow-md p-6 mb-6 dark:bg-transparent dark:text-white ">
|
||||||
<h1 class="text-3xl font-bold mb-6">Ergebnisse</h1>
|
<h1 class="text-3xl font-bold mb-6">Ergebnisse</h1>
|
||||||
|
{% if request.session.mode == "learn" or is_host %}
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
|
|
||||||
<h2 class="text-xl font-bold mb-4">Aktuelle Frage</h2>
|
<h2 class="text-xl font-bold mb-4">Aktuelle Frage</h2>
|
||||||
@@ -65,60 +65,57 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="flex flex-col items-center justify-center text-center pt-4">
|
||||||
|
{% if participant.last_answer_correct %}
|
||||||
|
<!-- Richtig-Symbol -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor"
|
||||||
|
class="text-green-500 size-40">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<!-- Feedback-Text -->
|
||||||
|
<div id="antwort-richtig"
|
||||||
|
class="pt-3 text-gray-600 dark:text-white font-bold max-w-xs md:max-w-sm">
|
||||||
|
Hier erscheint gleich dein Feedback...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% elif participant.last_answer_correct == False %}
|
||||||
|
<!-- Falsch-Symbol -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor"
|
||||||
|
class="text-red-500 size-40">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<!-- Feedback-Text -->
|
||||||
|
<div id="antwort-falsch"
|
||||||
|
class="pt-3 text-gray-600 dark:text-white font-bold max-w-xs md:max-w-sm">
|
||||||
|
Hier erscheint gleich dein Feedback...
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<p class="text-blue-600 font-bold text-md">+{{ participant.last_score }} Punkte</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
|
{% if request.session.mode == "learn" %}
|
||||||
<h2 class="text-xl font-bold mb-4">Punktestand</h2>
|
<h2 class="text-xl font-bold mb-4">Punktestand</h2>
|
||||||
|
{% endif %}
|
||||||
{% if is_last_question and request.session.mode != "learn" %}
|
|
||||||
<!-- Siegerpodest -->
|
|
||||||
<div class="flex justify-center items-end gap-4 mb-8">
|
|
||||||
|
|
||||||
<!-- Platz 2 links -->
|
|
||||||
{% with participant=participants.1 %}
|
|
||||||
<div class="flex flex-col items-center break-words">
|
|
||||||
<span class="display_name text-gray-600 font-black text-2xl dark:text-white " id="display_name-1">{{ participant.display_name }}</span>
|
|
||||||
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="dark:text-white text-gray-600 font-bold show-score text-md" id="score-1">{{ participant.score }}</p>
|
|
||||||
<div class="h-40 w-24 rounded-t-md font-bold text-center text-base shadow-lg bg-gradient-to-b from-gray-400 via-gray-300 to-gray-300 flex flex-col items-center justify-center text-black dark:text-white cursor-pointer leading-tight">
|
|
||||||
<span class="size-10 rounded-full bg-white text-black text-center flex items-center justify-center font-bold text-3xl">🥈</span><br>{{ participant.display_name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
<!-- Platz 1 mitte -->
|
|
||||||
{% with participant=participants.0 %}
|
|
||||||
|
|
||||||
<div class="flex flex-col items-center break-words">
|
|
||||||
<span class="display_name text-gray-600 font-black text-2xl dark:text-white" id="display_name-0">{{ participant.display_name }}</span>
|
|
||||||
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="dark:text-white text-gray-600 font-bold show-score text-md" id="score-0">{{ participant.score }}</p>
|
|
||||||
<div class="h-64 w-24 rounded-t-md font-bold text-center text-base shadow-lg bg-gradient-to-b from-yellow-400 via-yellow-300 to-yellow-600 flex flex-col items-center justify-center text-black dark:text-white cursor-pointer leading-tight">
|
|
||||||
<span class="size-12 rounded-full bg-white text-black text-center flex items-center justify-center font-bold text-4xl"> 🥇</span><br>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
<!-- Platz 3 rechts -->
|
|
||||||
{% with participant=participants.2 %}
|
|
||||||
<div class="flex flex-col items-center break-words">
|
|
||||||
<span class="display_name text-gray-600 font-black text-2xl dark:text-white " id="display_name-2">{{ participant.display_name }}</span>
|
|
||||||
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="dark:text-white text-gray-600 font-bold show-score text-md" id="score-2">{{ participant.score }}</p>
|
|
||||||
<div class="h-30 w-24 rounded-t-md font-bold text-center text-base shadow-lg bg-gradient-to-b from-orange-500 via-orange-400 to-orange-700 flex flex-col items-center justify-center text-black dark:text-white cursor-pointer leading-tight">
|
|
||||||
<span class="size-10 rounded-full bg-white text-black text-center flex items-center justify-center font-bold text-3xl ">🥉</span><br>{{ participant.display_name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="scoreboard" class="leading-relaxed">
|
<div id="scoreboard" class="leading-relaxed">
|
||||||
{% for participant in participants %}
|
{% for participant in participants %}
|
||||||
{% if not is_last_question or request.session.mode == "learn" %}
|
{% if request.session.mode == "learn" %}
|
||||||
{% if participant == my_participant or is_host %}
|
{% if participant == my_participant or is_host %}
|
||||||
<div class="dark:bg-transparent mt-2 shadow-sm p-4 bg-gray-100 rounded-lg border-2 border {% if forloop.counter <= 3 %}border-yellow-500{% else %}border-transparent{% endif %}">
|
<div class="dark:bg-transparent mt-2 shadow-sm p-4 bg-gray-100 rounded-lg border-2 border {% if forloop.counter <= 3 %}border-yellow-500{% else %}border-transparent{% endif %}">
|
||||||
<div class="flex flex-col items-start gap-1">
|
<div class="flex flex-col items-start gap-1">
|
||||||
@@ -189,13 +186,29 @@
|
|||||||
|
|
||||||
{% if is_host %}
|
{% if is_host %}
|
||||||
{% if is_last_question %}
|
{% if is_last_question %}
|
||||||
|
{% if is_last_question and request.session.mode != "learn" %}
|
||||||
|
<button id="winner-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||||
|
Siegerpodest
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
<button id="finish-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
<button id="finish-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||||
Quiz beenden
|
Quiz beenden
|
||||||
</button>
|
</button>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<button id="next-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
{% if request.session.mode == "learn" %}
|
||||||
|
<button id="next-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||||
Nächste Frage
|
Nächste Frage
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<button id="scoreboard-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||||
|
Punkteübersicht
|
||||||
|
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -205,23 +218,6 @@
|
|||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
{% if is_last_question %}
|
{% if is_last_question %}
|
||||||
|
|
||||||
<script>
|
|
||||||
// Hilfsfunktion für Pausen
|
|
||||||
function sleep(ms) {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function triggerConfetti() {
|
|
||||||
const end = Date.now() + 15 * 1000;
|
|
||||||
(function frame() {
|
|
||||||
confetti({ particleCount: 2, angle: 60, spread: 55, origin: { x: 0 } });
|
|
||||||
confetti({ particleCount: 2, angle: 120, spread: 55, origin: { x: 1 } });
|
|
||||||
if (Date.now() < end) requestAnimationFrame(frame);
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
triggerConfetti();
|
|
||||||
</script>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include 'play/game/common_scripts.html' %}
|
{% include 'play/game/common_scripts.html' %}
|
||||||
{% if is_host %}
|
{% if is_host %}
|
||||||
@@ -232,6 +228,16 @@
|
|||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
|
||||||
|
function triggerConfetti() {
|
||||||
|
const end = Date.now() + 30 * 1000;
|
||||||
|
(function frame() {
|
||||||
|
confetti({ particleCount: 2, angle: 60, spread: 55, origin: { x: 0 } });
|
||||||
|
confetti({ particleCount: 2, angle: 120, spread: 55, origin: { x: 1 } });
|
||||||
|
if (Date.now() < end) requestAnimationFrame(frame);
|
||||||
|
})();
|
||||||
|
}
|
||||||
async function start() {
|
async function start() {
|
||||||
|
|
||||||
elements=document.getElementsByClassName("show-score");
|
elements=document.getElementsByClassName("show-score");
|
||||||
@@ -269,6 +275,9 @@
|
|||||||
{% if is_last_question %}
|
{% if is_last_question %}
|
||||||
start();
|
start();
|
||||||
bounce();
|
bounce();
|
||||||
|
{% if request.session.mode == "learn" %}
|
||||||
|
triggerConfetti();
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -348,10 +357,11 @@ if (antwortRichtig) {
|
|||||||
gameSocket.onmessage = function(e) {
|
gameSocket.onmessage = function(e) {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
if (data.type === 'game_state_update' &&
|
if (data.type === 'game_state_update' &&
|
||||||
data.redirect_url &&
|
data.redirect_url &&
|
||||||
(data.action === 'next_question' || data.action === 'finish_game')) {
|
(data.action === 'next_question' || data.action === 'finish_game' || data.action === 'show_winner_podest'|| data.action === 'show_scoreboard')) {
|
||||||
window.location.href = data.redirect_url;
|
window.location.href = data.redirect_url;
|
||||||
} else if (data.type === 'answer_stats') {
|
}
|
||||||
|
else if (data.type === 'answer_stats') {
|
||||||
updateAnswerStats(data.stats);
|
updateAnswerStats(data.stats);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -377,7 +387,30 @@ if (antwortRichtig) {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var podestButton = document.getElementById('winner-button');
|
||||||
|
if (podestButton) {
|
||||||
|
podestButton.addEventListener('click', function() {
|
||||||
|
gameSocket.send(JSON.stringify({
|
||||||
|
'type': 'winner_podest',
|
||||||
|
'host_id': hostId
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
|
var scoreboardButton = document.getElementById('scoreboard-button');
|
||||||
|
if (scoreboardButton) {
|
||||||
|
scoreboardButton.addEventListener('click', function() {
|
||||||
|
gameSocket.send(JSON.stringify({
|
||||||
|
'type': 'scoreboard',
|
||||||
|
'host_id': hostId
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var nextButton = document.getElementById('next-button');
|
var nextButton = document.getElementById('next-button');
|
||||||
if (nextButton) {
|
if (nextButton) {
|
||||||
nextButton.addEventListener('click', function() {
|
nextButton.addEventListener('click', function() {
|
||||||
|
|||||||
310
django/templates/play/game/scoreboard.html
Normal file
310
django/templates/play/game/scoreboard.html
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container mx-auto px-4">
|
||||||
|
<div class="bg-white rounded-lg shadow-md p-6 mb-6 dark:bg-transparent dark:text-white">
|
||||||
|
<h1 class="text-3xl font-bold mb-6">Ergebnisse</h1>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="mb-6">
|
||||||
|
{% if request.session.mode != "learn" %}
|
||||||
|
<h2 class="text-xl font-bold mb-4">Punktestand</h2>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div id="scoreboard" class="leading-relaxed">
|
||||||
|
{% for participant in participants %}
|
||||||
|
{% if request.session.mode != "learn" %}
|
||||||
|
{% if participant == my_participant or is_host %}
|
||||||
|
<div class="dark:bg-transparent mt-2 shadow-sm p-4 bg-gray-100 rounded-lg border-2 border
|
||||||
|
{% if forloop.counter <= 3 %}
|
||||||
|
{% if forloop.counter == 1 %}border-yellow-400{% endif %}
|
||||||
|
{% if forloop.counter == 2 %}border-gray-400{% endif %}
|
||||||
|
{% if forloop.counter == 3 %}border-orange-400{% endif %}
|
||||||
|
{% else %}
|
||||||
|
border-transparent{% endif %}">
|
||||||
|
<div class="flex flex-col items-start gap-1">
|
||||||
|
<div class="text-4xl font-bold flex items-center gap-2">
|
||||||
|
{% if forloop.counter == 1 %}
|
||||||
|
🥇
|
||||||
|
{% elif forloop.counter == 2 %}
|
||||||
|
🥈
|
||||||
|
{% elif forloop.counter == 3 %}
|
||||||
|
🥉
|
||||||
|
{% else %}
|
||||||
|
{{ forloop.counter }}.
|
||||||
|
{% endif %}
|
||||||
|
<span class="display_name text-gray-600 font-black text-3xl dark:text-white " id="display_name-{{ forloop.counter0 }}">{{ participant.display_name }}</span>
|
||||||
|
</div class="flex flex-col items-start">
|
||||||
|
|
||||||
|
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="dark:text-white text-gray-600 font-bold show-score text-xl" id="score-{{ forloop.counter0 }}">{{ participant.score }} Punkte</p>
|
||||||
|
{% if participant == my_participant or request.session.mode == "learn"%}
|
||||||
|
<p class="text-blue-600 font-bold text-md">+{{ participant.last_score }} Punkte</p>
|
||||||
|
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if is_host %}
|
||||||
|
{% if is_last_question %}
|
||||||
|
{% if is_last_question and request.session.mode != "learn" %}
|
||||||
|
<button id="winner-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||||
|
Siegerpodest
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<button id="finish-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||||
|
Quiz beenden
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
<button id="next-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||||
|
Nächste Frage
|
||||||
|
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
{% if is_last_question %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% include 'play/game/common_scripts.html' %}
|
||||||
|
{% if is_host %}
|
||||||
|
{% include 'play/game/sound.html' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function start() {
|
||||||
|
|
||||||
|
elements=document.getElementsByClassName("show-score");
|
||||||
|
for (let i = 0; i < elements.length; i++) {
|
||||||
|
const el = elements[i];
|
||||||
|
|
||||||
|
let counter=Number(el.getAttribute('data-score'))-Number(el.getAttribute('data-last-score'));
|
||||||
|
let goal=Number(el.getAttribute('data-score'));
|
||||||
|
let interval= setInterval(function(){
|
||||||
|
el.innerHTML = counter + " Punkte";
|
||||||
|
|
||||||
|
|
||||||
|
if(counter<=goal){
|
||||||
|
el.innerHTML = counter + " Punkte";
|
||||||
|
counter+=8;
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
|
||||||
|
el.innerHTML = goal + " Punkte";
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 20);}}
|
||||||
|
|
||||||
|
function bounce(){
|
||||||
|
|
||||||
|
const names = document.getElementsByClassName("display_name");
|
||||||
|
for (let i = 0; i < names.length; i++) {
|
||||||
|
names[i].classList.add("animate-bounce");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{% if is_last_question %}
|
||||||
|
start();
|
||||||
|
bounce();
|
||||||
|
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
start();
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
const richtigeAntworten = [
|
||||||
|
"Deine Antwort ist nicht falsch. Sie ist richtig!",
|
||||||
|
"Ja, du kannst es!",
|
||||||
|
"So kann es weiter gehen.",
|
||||||
|
"Ja, deine Antwort ist korrekt. Was soll ich dazu noch sagen? SUPER!",
|
||||||
|
"SUPER GUT.",
|
||||||
|
"Volltreffer!",
|
||||||
|
"Bravo! Das sitzt!",
|
||||||
|
"Na bitte, geht doch!",
|
||||||
|
"Schön. Einfach schön.",
|
||||||
|
"Exzellent kombiniert, Sherlock!",
|
||||||
|
"Punkt für dich!",
|
||||||
|
"Das war meisterhaft.",
|
||||||
|
"Korrekt - du scheinst das zu können!",
|
||||||
|
"So sieht Erfolg aus!",
|
||||||
|
"Glänzend gelöst!",
|
||||||
|
"Das war keine Glückssache - das war Können!",
|
||||||
|
"Treffer, versenkt!",
|
||||||
|
"Du hast es drauf!",
|
||||||
|
"Da merkt man: Profi am Werk.",
|
||||||
|
"Richtige Antwort - dafür gibt es viele Punkte!",
|
||||||
|
"Weiter so!",
|
||||||
|
"Wow - das war richtig gut!"
|
||||||
|
];
|
||||||
|
|
||||||
|
const falscheAntworten = [
|
||||||
|
"Oops! War wohl nicht dein Tag.",
|
||||||
|
"Knapp daneben ist auch vorbei!",
|
||||||
|
"Das war... kreativ!",
|
||||||
|
"Wenn man schon alles wüsste, wäre das Quiz doch sinnlos.",
|
||||||
|
"Deine Antwort war einzigartig - leider falsch!",
|
||||||
|
"Na gut, wenigstens hast du es probiert.",
|
||||||
|
"Das war eine interessante Theorie...",
|
||||||
|
"Ein Genie irrt sich auch mal.",
|
||||||
|
"Eine gute Antwort - aber nicht auf diese Frage.",
|
||||||
|
"Das ist leider falsch. Mehr gibt es nicht zu sagen.",
|
||||||
|
"Gute Idee! Aber leider nicht richtig.",
|
||||||
|
"Oh weh! Ein Fehler. Naja, einer ist keiner.",
|
||||||
|
"Die Antwort klang richtig, sie ist es aber nicht.",
|
||||||
|
"Interessanter Ansatz. Nicht hilfreich, aber interessant.",
|
||||||
|
"Nenn das lieber „alternative Fakten“.",
|
||||||
|
"Du hast die richtige Antwort doch gewusst: 'Die Seite hat nur nicht geladen.'",
|
||||||
|
"Jeder fängt mal klein an!",
|
||||||
|
"Kopf hoch - weiter geht's!",
|
||||||
|
"Nobody is perfect!",
|
||||||
|
"Du schaffst das - vielleicht ab jetzt.",
|
||||||
|
"Falsche Antwort, aber guter Wille!",
|
||||||
|
"Übung macht den Quiz-Meister.",
|
||||||
|
"Beim nächsten Mal triffst du!",
|
||||||
|
"Das war nur zum Aufwärmen, oder?",
|
||||||
|
"Kein Problem - weiter geht's.",
|
||||||
|
"Gute Frage - aber falsche Antwort."
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const antwortRichtig = document.getElementById("antwort-richtig");
|
||||||
|
const antwortFalsch = document.getElementById("antwort-falsch");
|
||||||
|
|
||||||
|
if (antwortRichtig) {
|
||||||
|
antwortRichtig.textContent = richtigeAntworten[Math.floor(Math.random() * richtigeAntworten.length)];
|
||||||
|
} else if (antwortFalsch) {
|
||||||
|
antwortFalsch.textContent = falscheAntworten[Math.floor(Math.random() * falscheAntworten.length)];
|
||||||
|
}
|
||||||
|
const joinCode = '{{ quiz_game.join_code }}';
|
||||||
|
const hostId = '{{ host_id }}';
|
||||||
|
const wsScheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
|
const gameSocket = new WebSocket(
|
||||||
|
`${wsScheme}://${window.location.host}/ws/game/${joinCode}/`
|
||||||
|
);
|
||||||
|
|
||||||
|
gameSocket.onmessage = function(e) {
|
||||||
|
const data = JSON.parse(e.data);
|
||||||
|
if (data.type === 'game_state_update' &&
|
||||||
|
data.redirect_url &&
|
||||||
|
(data.action === 'next_question' || data.action === 'finish_game' || data.action === 'show_winner_podest'|| data.action === 'show_scoreboard')) {
|
||||||
|
window.location.href = data.redirect_url;
|
||||||
|
}
|
||||||
|
else if (data.type === 'answer_stats') {
|
||||||
|
updateAnswerStats(data.stats);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
gameSocket.onclose = function(e) {
|
||||||
|
// Normal closure (code 1000) oder Navigation zu einer anderen Seite (code 1001)
|
||||||
|
if (e.code === 1000 || e.code === 1001) {
|
||||||
|
console.log('Game socket closed normally');
|
||||||
|
} else {
|
||||||
|
console.error('Game socket closed unexpectedly', e.code);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
{% if is_host %}
|
||||||
|
// Add click handlers for host buttons
|
||||||
|
{% if is_last_question %}
|
||||||
|
var finishButton = document.getElementById('finish-button');
|
||||||
|
if (finishButton) {
|
||||||
|
finishButton.addEventListener('click', function() {
|
||||||
|
gameSocket.send(JSON.stringify({
|
||||||
|
'type': 'finish_game',
|
||||||
|
'host_id': hostId
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var podestButton = document.getElementById('winner-button');
|
||||||
|
if (podestButton) {
|
||||||
|
podestButton.addEventListener('click', function() {
|
||||||
|
gameSocket.send(JSON.stringify({
|
||||||
|
'type': 'winner_podest',
|
||||||
|
'host_id': hostId
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
var scoreboardButton = document.getElementById('scoreboard-button');
|
||||||
|
if (scoreboardButton) {
|
||||||
|
scoreboardButton.addEventListener('click', function() {
|
||||||
|
gameSocket.send(JSON.stringify({
|
||||||
|
'type': 'scoreboard',
|
||||||
|
'host_id': hostId
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var nextButton = document.getElementById('next-button');
|
||||||
|
if (nextButton) {
|
||||||
|
nextButton.addEventListener('click', function() {
|
||||||
|
gameSocket.send(JSON.stringify({
|
||||||
|
'type': 'next_question',
|
||||||
|
'host_id': hostId
|
||||||
|
}));
|
||||||
|
}, { once: true });
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
function updateAnswerStats(stats) {
|
||||||
|
const total = Object.values(stats).reduce((a, b) => a + b, 0) || 1;
|
||||||
|
|
||||||
|
for (var optionIndex in stats) {
|
||||||
|
if (stats.hasOwnProperty(optionIndex)) {
|
||||||
|
var countElement = document.getElementById('option-count-' + optionIndex);
|
||||||
|
var fillElement = document.getElementById('option-fill-' + optionIndex);
|
||||||
|
|
||||||
|
if (countElement) {
|
||||||
|
countElement.textContent = stats[optionIndex] + ' Antwort(en)';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fillElement) {
|
||||||
|
const percentage = Math.round((stats[optionIndex] / total) * 100);
|
||||||
|
fillElement.style.width = percentage + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request answer stats when page loads
|
||||||
|
gameSocket.onopen = function(e) {
|
||||||
|
gameSocket.send(JSON.stringify({
|
||||||
|
'type': 'get_answer_stats'
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
594
django/templates/play/game/winner_podest.html
Normal file
594
django/templates/play/game/winner_podest.html
Normal file
@@ -0,0 +1,594 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% if is_last_question and is_host %}
|
||||||
|
<!-- Siegerpodest -->
|
||||||
|
<div class="min-h-screen">
|
||||||
|
<h2 class="w-full flex justify-center dark:text-white font-bold text-2xl ">🏆 Siegerpodest</h2>
|
||||||
|
<h4 class="w-full flex justify-center dark:text-white font-bold">Wer ist hier der Quiz-Master? </h4>
|
||||||
|
|
||||||
|
{% with participant=participants.0 %}
|
||||||
|
<div class="w-full flex justify-center">
|
||||||
|
<h1 id="winner-text" class="bg-gradient-to-r from-yellow-400 via-yellow-300 to-yellow-600 bg-clip-text text-transparent font-bold hidden animate-ping px-2 max-w-screen-sm text-center break-words">
|
||||||
|
Glückwunsch, {{ participant.display_name }}!
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div id="podest-container" class="w-full absolute bottom-0 flex justify-center items-end gap-2 md:gap-4 mb-2 dark:text-white">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Platz 2 links -->
|
||||||
|
{% with participant=participants.1 %}
|
||||||
|
<div class="flex flex-col shrink items-center break-words max-w-[6rem] md:max-w-[8rem] lg:max-w-[10rem] overflow-x-auto pt-10">
|
||||||
|
<span class="display_name p-2 text-gray-600 font-black text-xl md:text-2xl lg:text-3xl opacity-0 transition-opacity duration-1000 dark:text-white hyphens-auto" id="display_name-1">{{ participant.display_name }}</span>
|
||||||
|
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="opacity-0 transition-opacity duration-1000 dark:text-white font-bold show-score text-sm md:text-lg lg:text-xl text-gray-400" id="score-1">{{ participant.score }}</p>
|
||||||
|
<div data-final-height="33" id="podium-1" class="border-2 border-gray-200 transition-all duration-1000 ease-out overflow-hidden podium h-24 md:h-[12rem] lg:h-[16rem] w-24 md:w-32 lg:w-40 rounded-t-md font-bold text-center text-base shadow-lg bg-gradient-to-b from-gray-400 via-gray-300 to-gray-300 flex flex-col items-center justify-center text-black dark:text-white cursor-pointer leading-tight">
|
||||||
|
<span class="size-10 md:size-18 rounded-full bg-white text-black text-center flex items-center justify-center font-bold text-3xl md:text-5xl lg:text-6xl">🥈</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<!-- Platz 1 mitte -->
|
||||||
|
{% with participant=participants.0 %}
|
||||||
|
<div class="flex flex-col shrink items-center break-words max-w-[6rem] md:max-w-[8rem] lg:max-w-[10rem] overflow-x-auto pt-10">
|
||||||
|
<div id="resultFirst"></div>
|
||||||
|
<span class="display_name p-2 text-gray-600 font-black text-xl md:text-3xl lg:text-4xl opacity-0 transition-opacity duration-1000 bg-gradient-to-r from-yellow-400 via-yellow-300 to-yellow-600 bg-clip-text text-transparent hyphens-auto" id="display_name-0">{{ participant.display_name }}</span>
|
||||||
|
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="opacity-0 transition-opacity duration-1000 dark:text-white text-yellow-500 font-bold show-score text-sm md:text-lg lg:text-xl" id="score-0">{{ participant.score }}</p>
|
||||||
|
<div data-final-height="45" id="podium-0" class="border-2 border-yellow-500 transition-all duration-1000 ease-out overflow-hidden podium h-32 md:h-[16rem] lg:h-[22rem] w-24 md:w-32 lg:w-40 rounded-t-md font-bold text-center text-base shadow-lg bg-gradient-to-b from-yellow-400 via-yellow-300 to-yellow-600 flex flex-col items-center justify-center text-black dark:text-white cursor-pointer leading-tight">
|
||||||
|
<span id="firstMedal" class="size-12 md:size-22 rounded-full bg-white text-black text-center flex items-center justify-center font-bold text-4xl md:text-6xl lg:text-7xl">🥇</span><br>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<!-- Platz 3 rechts -->
|
||||||
|
{% with participant=participants.2 %}
|
||||||
|
<div class="flex flex-col shrink items-center break-words max-w-[6rem] md:max-w-[8rem] lg:max-w-[10rem] overflow-x-auto pt-10">
|
||||||
|
<span class="display_name p-2 text-gray-600 font-black text-xl md:text-2xl lg:text-3xl dark:text-white opacity-0 transition-opacity duration-1000 hyphens-auto" id="display_name-2">{{ participant.display_name }}</span>
|
||||||
|
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="opacity-0 transition-opacity duration-1000 dark:text-white text-orange-500 font-bold show-score text-sm md:text-lg lg:text-xl" id="score-2">{{ participant.score }}</p>
|
||||||
|
<div data-final-height="25" id="podium-2" class="border-2 border-orange-500 transition-all duration-1000 ease-out overflow-hidden podium h-20 md:h-[10rem] lg:h-[14rem] w-24 md:w-32 lg:w-40 rounded-t-md font-bold text-center text-base shadow-lg bg-gradient-to-b from-orange-500 via-orange-400 to-orange-700 flex flex-col items-center justify-center text-black dark:text-white cursor-pointer leading-tight">
|
||||||
|
<span class="size-10 md:size-18 rounded-full bg-white text-black text-center flex items-center justify-center font-bold text-3xl md:text-5xl lg:text-6xl">🥉</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="thanks" class="hidden -mt-10">
|
||||||
|
<div class="w-full flex justify-center">
|
||||||
|
<h1 class="bg-gradient-to-r from-yellow-400 via-yellow-300 to-yellow-600 bg-clip-text text-transparent font-bold px-2 max-w-screen-sm text-center break-words">
|
||||||
|
Vielen Dank fürs Spielen!
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<button id="finish-button" class=" w-3/4 md:w-1/2 bottom-0 left-0 p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||||
|
Quiz beenden
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container mx-auto px-4">
|
||||||
|
|
||||||
|
|
||||||
|
{% with participant=participants.0 %}
|
||||||
|
<div class="w-full flex justify-center">
|
||||||
|
<h1 id="winner-text" class="bg-gradient-to-r from-yellow-400 via-yellow-300 to-yellow-600 bg-clip-text text-transparent font-bold hidden animate-ping px-2 max-w-screen-sm text-center break-words">
|
||||||
|
Glückwunsch, {{ participant.display_name }}!
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg shadow-md p-6 mb-6 dark:bg-transparent dark:text-white">
|
||||||
|
<h1 id="result-text" class="text-3xl w-full flex justify-center dark:text-white font-bold">🏆 Wer ist hier der Quiz-Master?</h1>
|
||||||
|
|
||||||
|
<h4 id="info" class="mt-4 w-full flex justify-center dark:text-white font-bold">Nach der Siegerehrung siehst du deine Platzierung!</h4>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div id="participant-score" class="mb-6 ">
|
||||||
|
|
||||||
|
|
||||||
|
<div id="scoreboard" class="leading-relaxed">
|
||||||
|
{% for participant in participants %}
|
||||||
|
{% if request.session.mode != "learn" %}
|
||||||
|
{% if participant == my_participant or is_host %}
|
||||||
|
<div id="player_score" class="hidden dark:bg-transparent mt-2 shadow-sm p-4 bg-gray-100 rounded-lg border-2 border {% if forloop.counter <= 3 %}
|
||||||
|
{% if forloop.counter == 1 %}border-yellow-400{% endif %}
|
||||||
|
{% if forloop.counter == 2 %}border-gray-400{% endif %}
|
||||||
|
{% if forloop.counter == 3 %}border-orange-400{% endif %}
|
||||||
|
{% else %}
|
||||||
|
border-transparent{% endif %}">
|
||||||
|
<div class="flex flex-col items-start gap-1">
|
||||||
|
<div class="text-4xl font-bold flex items-center gap-2">
|
||||||
|
{% if forloop.counter == 1 %}
|
||||||
|
🥇
|
||||||
|
{% elif forloop.counter == 2 %}
|
||||||
|
🥈
|
||||||
|
{% elif forloop.counter == 3 %}
|
||||||
|
🥉
|
||||||
|
{% else %}
|
||||||
|
{{ forloop.counter }}.
|
||||||
|
{% endif %}
|
||||||
|
<span class="display_name text-gray-600 font-black text-3xl dark:text-white " id="display_name-{{ forloop.counter0 }}">{{ participant.display_name }}</span>
|
||||||
|
</div class="flex flex-col items-start">
|
||||||
|
|
||||||
|
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="dark:text-white text-gray-600 font-bold show-score text-xl" id="score-{{ forloop.counter0 }}">{{ participant.score }} Punkte</p>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.podium {
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
.spotlight-wrapper::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -800px; /* Start oben außerhalb */
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 15rem;
|
||||||
|
height: 700px;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
background: radial-gradient(
|
||||||
|
ellipse 60% 100% at center bottom,
|
||||||
|
rgba(254, 254, 178, 0.9) 0%,
|
||||||
|
rgba(252, 236, 132, 0.7) 25%,
|
||||||
|
rgba(255, 230, 130, 0.5) 55%,
|
||||||
|
rgba(255, 210, 100, 0.1) 85%,
|
||||||
|
rgba(255, 180, 70, 0) 100%
|
||||||
|
);
|
||||||
|
|
||||||
|
filter: blur(24px) brightness(1.2);
|
||||||
|
opacity: 0; /* Start unsichtbar */
|
||||||
|
animation: slideInOutSpotlight 6s ease forwards , fadeOutSpotlight 6s 6s ease forwards; /* Dauer anpassen */
|
||||||
|
z-index: -1;
|
||||||
|
|
||||||
|
box-shadow: 0 0 60px 30px rgba(255, 240, 150, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInOutSpotlight {
|
||||||
|
0% {
|
||||||
|
top: -800px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
top: -640px; /* Endposition */
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
top: -640px;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes fadeOutSpotlight {
|
||||||
|
0% {
|
||||||
|
top: -640px;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
100% {
|
||||||
|
top: -640px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
{% if is_last_question %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
const animationDuration = 1500; // Dauer der Wachstumsanimation
|
||||||
|
const waitTime=3000;
|
||||||
|
const podiums = [
|
||||||
|
|
||||||
|
{ id: 'podium-2', delay: 2000 }, // immer vorheriges von Bedeutung
|
||||||
|
{ id: 'podium-1', delay: 2000 },
|
||||||
|
{ id: 'podium-0', delay: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
let totalDelay = 0;
|
||||||
|
|
||||||
|
podiums.forEach(({ delay }) => {
|
||||||
|
totalDelay += delay + waitTime;
|
||||||
|
});
|
||||||
|
const totalAnimationTime = totalDelay + animationDuration;
|
||||||
|
setTimeout(() => {
|
||||||
|
show_score();
|
||||||
|
}, totalAnimationTime);
|
||||||
|
|
||||||
|
totalDelay = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
podiums.forEach(({ id, delay }) => {
|
||||||
|
|
||||||
|
const podium = document.getElementById(id);
|
||||||
|
if (!podium) return;
|
||||||
|
|
||||||
|
const finalHeight = parseInt(podium.dataset.finalHeight) || 160;
|
||||||
|
podium.style.transition = `height ${animationDuration}ms ease-in-out`;
|
||||||
|
podium.style.height = '0px';
|
||||||
|
|
||||||
|
// Wachstum starten nach gesamter Wartezeit
|
||||||
|
setTimeout(() => {
|
||||||
|
podium.style.height = `${finalHeight}vh`;
|
||||||
|
}, totalDelay);
|
||||||
|
|
||||||
|
// Name & Score anzeigen nach Balken-Animation
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
if (id === 'podium-0') {
|
||||||
|
let name = document.getElementById(`display_name-0`);
|
||||||
|
let score = document.getElementById(`score-0`);
|
||||||
|
let winnerText= document.getElementById(`winner-text`);
|
||||||
|
|
||||||
|
let thanks= document.getElementById(`thanks`);
|
||||||
|
// 1. Lichtkegel anzeigen
|
||||||
|
if (resultFirst) {
|
||||||
|
resultFirst.classList.add('spotlight-wrapper');
|
||||||
|
score.classList.add('opacity-0');
|
||||||
|
name.classList.add('opacity-0');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 2. Nach kurzer Wartezeit Name & Score anzeigen
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
name.classList.add('opacity-100', 'animate-bounce','spotlight-text'); // z. B. sichtbar + Effekt
|
||||||
|
}
|
||||||
|
|
||||||
|
if (winnerText) {
|
||||||
|
winnerText.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thanks) {
|
||||||
|
thanks.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (score) {
|
||||||
|
score.classList.add('opacity-100'); // z. B. sichtbar + Effekt
|
||||||
|
}
|
||||||
|
triggerConfetti();
|
||||||
|
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
document.body.style.overflow = 'auto';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = id.split('-')[1];
|
||||||
|
const name = document.getElementById(`display_name-${index}`);
|
||||||
|
const score = document.getElementById(`score-${index}`);
|
||||||
|
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
if (name && id !== 'podium-0') setTimeout(() => {
|
||||||
|
name.classList.add('opacity-100');
|
||||||
|
name.classList.add('animate-bounce');
|
||||||
|
|
||||||
|
if (score) score.classList.add('opacity-100');
|
||||||
|
}, waitTime);
|
||||||
|
|
||||||
|
}, totalDelay + animationDuration);
|
||||||
|
|
||||||
|
|
||||||
|
totalDelay += delay + waitTime; // wichtig: aufsummieren
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hilfsfunktion für Pausen
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function triggerConfetti() {
|
||||||
|
const end = Date.now() + 30 * 1000;
|
||||||
|
(function frame() {
|
||||||
|
confetti({ particleCount: 2, angle: 60, spread: 55, origin: { x: 0 } });
|
||||||
|
confetti({ particleCount: 2, angle: 120, spread: 55, origin: { x: 1 } });
|
||||||
|
if (Date.now() < end) requestAnimationFrame(frame);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_score() {
|
||||||
|
|
||||||
|
let player_score=document.getElementById('player_score');
|
||||||
|
player_score.classList.remove('hidden');
|
||||||
|
info=document.getElementById('info');
|
||||||
|
info.classList.add('hidden');
|
||||||
|
resultText=document.getElementById('result-text');
|
||||||
|
resultText.textContent="Dein Ergebnis";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const myName = "{{ my_participant.id|escapejs }}";
|
||||||
|
const firstPlaceName = "{{ participants.0.id|escapejs }}";
|
||||||
|
|
||||||
|
if (myName === firstPlaceName) {
|
||||||
|
let winnerText= document.getElementById(`winner-text`);
|
||||||
|
winnerText.classList.remove('hidden');
|
||||||
|
triggerConfetti();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% include 'play/game/common_scripts.html' %}
|
||||||
|
{% if is_host %}
|
||||||
|
{% include 'play/game/sound.html' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function start() {
|
||||||
|
|
||||||
|
elements=document.getElementsByClassName("show-score");
|
||||||
|
for (let i = 0; i < elements.length; i++) {
|
||||||
|
const el = elements[i];
|
||||||
|
|
||||||
|
let counter=Number(el.getAttribute('data-score'))-Number(el.getAttribute('data-last-score'));
|
||||||
|
let goal=Number(el.getAttribute('data-score'));
|
||||||
|
let interval= setInterval(function(){
|
||||||
|
el.innerHTML = counter + " Punkte";
|
||||||
|
|
||||||
|
|
||||||
|
if(counter<=goal){
|
||||||
|
el.innerHTML = counter + " Punkte";
|
||||||
|
counter+=8;
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
|
||||||
|
el.innerHTML = goal + " Punkte";
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 20);}}
|
||||||
|
|
||||||
|
function bounce(){
|
||||||
|
|
||||||
|
const names = document.getElementsByClassName("display_name");
|
||||||
|
for (let i = 0; i < names.length; i++) {
|
||||||
|
names[i].classList.add("animate-bounce");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{% if is_last_question %}
|
||||||
|
start();
|
||||||
|
bounce();
|
||||||
|
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
start();
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
const richtigeAntworten = [
|
||||||
|
"Deine Antwort ist nicht falsch. Sie ist richtig!",
|
||||||
|
"Ja, du kannst es!",
|
||||||
|
"So kann es weiter gehen.",
|
||||||
|
"Ja, deine Antwort ist korrekt. Was soll ich dazu noch sagen? SUPER!",
|
||||||
|
"SUPER GUT.",
|
||||||
|
"Volltreffer!",
|
||||||
|
"Bravo! Das sitzt!",
|
||||||
|
"Na bitte, geht doch!",
|
||||||
|
"Schön. Einfach schön.",
|
||||||
|
"Exzellent kombiniert, Sherlock!",
|
||||||
|
"Punkt für dich!",
|
||||||
|
"Das war meisterhaft.",
|
||||||
|
"Korrekt - du scheinst das zu können!",
|
||||||
|
"So sieht Erfolg aus!",
|
||||||
|
"Glänzend gelöst!",
|
||||||
|
"Das war keine Glückssache - das war Können!",
|
||||||
|
"Treffer, versenkt!",
|
||||||
|
"Du hast es drauf!",
|
||||||
|
"Da merkt man: Profi am Werk.",
|
||||||
|
"Richtige Antwort - dafür gibt es viele Punkte!",
|
||||||
|
"Weiter so!",
|
||||||
|
"Wow - das war richtig gut!"
|
||||||
|
];
|
||||||
|
|
||||||
|
const falscheAntworten = [
|
||||||
|
"Oops! War wohl nicht dein Tag.",
|
||||||
|
"Knapp daneben ist auch vorbei!",
|
||||||
|
"Das war... kreativ!",
|
||||||
|
"Wenn man schon alles wüsste, wäre das Quiz doch sinnlos.",
|
||||||
|
"Deine Antwort war einzigartig - leider falsch!",
|
||||||
|
"Na gut, wenigstens hast du es probiert.",
|
||||||
|
"Das war eine interessante Theorie...",
|
||||||
|
"Ein Genie irrt sich auch mal.",
|
||||||
|
"Eine gute Antwort - aber nicht auf diese Frage.",
|
||||||
|
"Das ist leider falsch. Mehr gibt es nicht zu sagen.",
|
||||||
|
"Gute Idee! Aber leider nicht richtig.",
|
||||||
|
"Oh weh! Ein Fehler. Naja, einer ist keiner.",
|
||||||
|
"Die Antwort klang richtig, sie ist es aber nicht.",
|
||||||
|
"Interessanter Ansatz. Nicht hilfreich, aber interessant.",
|
||||||
|
"Nenn das lieber „alternative Fakten“.",
|
||||||
|
"Du hast die richtige Antwort doch gewusst: 'Die Seite hat nur nicht geladen.'",
|
||||||
|
"Jeder fängt mal klein an!",
|
||||||
|
"Kopf hoch - weiter geht's!",
|
||||||
|
"Nobody is perfect!",
|
||||||
|
"Du schaffst das - vielleicht ab jetzt.",
|
||||||
|
"Falsche Antwort, aber guter Wille!",
|
||||||
|
"Übung macht den Quiz-Meister.",
|
||||||
|
"Beim nächsten Mal triffst du!",
|
||||||
|
"Das war nur zum Aufwärmen, oder?",
|
||||||
|
"Kein Problem - weiter geht's.",
|
||||||
|
"Gute Frage - aber falsche Antwort."
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const antwortRichtig = document.getElementById("antwort-richtig");
|
||||||
|
const antwortFalsch = document.getElementById("antwort-falsch");
|
||||||
|
|
||||||
|
if (antwortRichtig) {
|
||||||
|
antwortRichtig.textContent = richtigeAntworten[Math.floor(Math.random() * richtigeAntworten.length)];
|
||||||
|
} else if (antwortFalsch) {
|
||||||
|
antwortFalsch.textContent = falscheAntworten[Math.floor(Math.random() * falscheAntworten.length)];
|
||||||
|
}
|
||||||
|
const joinCode = '{{ quiz_game.join_code }}';
|
||||||
|
const hostId = '{{ host_id }}';
|
||||||
|
const wsScheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
|
const gameSocket = new WebSocket(
|
||||||
|
`${wsScheme}://${window.location.host}/ws/game/${joinCode}/`
|
||||||
|
);
|
||||||
|
|
||||||
|
gameSocket.onmessage = function(e) {
|
||||||
|
const data = JSON.parse(e.data);
|
||||||
|
if (data.type === 'game_state_update' &&
|
||||||
|
data.redirect_url &&
|
||||||
|
(data.action === 'next_question' || data.action === 'finish_game' || data.action === 'show_winner_podest')) {
|
||||||
|
window.location.href = data.redirect_url;
|
||||||
|
}
|
||||||
|
else if (data.type === 'answer_stats') {
|
||||||
|
updateAnswerStats(data.stats);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
gameSocket.onclose = function(e) {
|
||||||
|
// Normal closure (code 1000) oder Navigation zu einer anderen Seite (code 1001)
|
||||||
|
if (e.code === 1000 || e.code === 1001) {
|
||||||
|
console.log('Game socket closed normally');
|
||||||
|
} else {
|
||||||
|
console.error('Game socket closed unexpectedly', e.code);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
{% if is_host %}
|
||||||
|
// Add click handlers for host buttons
|
||||||
|
{% if is_last_question %}
|
||||||
|
var finishButton = document.getElementById('finish-button');
|
||||||
|
if (finishButton) {
|
||||||
|
finishButton.addEventListener('click', function() {
|
||||||
|
gameSocket.send(JSON.stringify({
|
||||||
|
'type': 'finish_game',
|
||||||
|
'host_id': hostId
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var podestButton = document.getElementById('winner-button');
|
||||||
|
if (podestButton) {
|
||||||
|
podestButton.addEventListener('click', function() {
|
||||||
|
gameSocket.send(JSON.stringify({
|
||||||
|
'type': 'winner_podest',
|
||||||
|
'host_id': hostId
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
var nextButton = document.getElementById('next-button');
|
||||||
|
if (nextButton) {
|
||||||
|
nextButton.addEventListener('click', function() {
|
||||||
|
gameSocket.send(JSON.stringify({
|
||||||
|
'type': 'next_question',
|
||||||
|
'host_id': hostId
|
||||||
|
}));
|
||||||
|
}, { once: true });
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
function updateAnswerStats(stats) {
|
||||||
|
const total = Object.values(stats).reduce((a, b) => a + b, 0) || 1;
|
||||||
|
|
||||||
|
for (var optionIndex in stats) {
|
||||||
|
if (stats.hasOwnProperty(optionIndex)) {
|
||||||
|
var countElement = document.getElementById('option-count-' + optionIndex);
|
||||||
|
var fillElement = document.getElementById('option-fill-' + optionIndex);
|
||||||
|
|
||||||
|
if (countElement) {
|
||||||
|
countElement.textContent = stats[optionIndex] + ' Antwort(en)';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fillElement) {
|
||||||
|
const percentage = Math.round((stats[optionIndex] / total) * 100);
|
||||||
|
fillElement.style.width = percentage + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request answer stats when page loads
|
||||||
|
gameSocket.onopen = function(e) {
|
||||||
|
gameSocket.send(JSON.stringify({
|
||||||
|
'type': 'get_answer_stats'
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user