545 lines
23 KiB
HTML
545 lines
23 KiB
HTML
{% 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>
|
|
{% if request.session.mode == "learn" or is_host %}
|
|
<div class="mb-6">
|
|
|
|
<h2 class="text-xl font-bold mb-4">Aktuelle Frage</h2>
|
|
<p class="text-lg mb-2">{{ question_data.question }}</p>
|
|
|
|
{% if question_data.type == "map" %}
|
|
<!-- Map Question Display -->
|
|
<div class="mb-4">
|
|
<div id="map" class="h-96 w-full rounded-lg border border-gray-300 dark:border-gray-600"></div>
|
|
</div>
|
|
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
|
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
|
crossorigin=""></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Parse the target location from the string format "lat,lng"
|
|
const targetCoords = '{{ question_data.target_location|escapejs }}'.split(',');
|
|
let targetLat, targetLng;
|
|
|
|
if (targetCoords.length === 2) {
|
|
targetLat = parseFloat(targetCoords[0].trim());
|
|
targetLng = parseFloat(targetCoords[1].trim());
|
|
|
|
// Normalize longitude to be within -180 to 180
|
|
targetLng = ((targetLng % 360) + 540) % 360 - 180;
|
|
|
|
// Clamp latitude to valid range (-90 to 90)
|
|
targetLat = Math.max(-90, Math.min(90, targetLat));
|
|
|
|
console.log('Normalized coordinates:', { lat: targetLat, lng: targetLng });
|
|
}
|
|
|
|
// Validate coordinates
|
|
if (isNaN(targetLat) || isNaN(targetLng)) {
|
|
console.warn('Invalid coordinates, hiding map');
|
|
document.getElementById('map-container').style.display = 'none';
|
|
return; // Exit the function early
|
|
}
|
|
|
|
const targetLocation = [targetLat, targetLng];
|
|
console.log('Using coordinates:', targetLocation);
|
|
|
|
// Initialize map centered on target location
|
|
const map = L.map('map').setView(targetLocation, 7);
|
|
|
|
// Check for dark mode
|
|
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
|
|
// Choose appropriate map style
|
|
const tileUrl = isDarkMode
|
|
? 'https://{s}.basemaps.cartocdn.com/rastertiles/dark_all/{z}/{x}/{y}.png'
|
|
: 'https://{s}.basemaps.cartocdn.com/rastertiles/light_all/{z}/{x}/{y}.png';
|
|
|
|
L.tileLayer(tileUrl, {
|
|
maxZoom: 19,
|
|
attribution: '© OpenStreetMap © CARTO',
|
|
noWrap: true
|
|
}).addTo(map);
|
|
|
|
// Add target marker
|
|
L.marker(targetLocation, {
|
|
icon: L.divIcon({
|
|
html: '🎯',
|
|
className: 'text-3xl',
|
|
iconSize: [32, 32],
|
|
iconAnchor: [16, 32],
|
|
popupAnchor: [0, -32]
|
|
})
|
|
}).addTo(map)
|
|
.bindPopup('Zielort')
|
|
.openPopup();
|
|
|
|
// Add tolerance radius circle if available
|
|
{% if question_data.tolerance_radius %}
|
|
(function() {
|
|
try {
|
|
const toleranceRadius = parseFloat('{{ question_data.tolerance_radius|escapejs }}'.replace(',', '.'));
|
|
if (!isNaN(toleranceRadius) && toleranceRadius > 0) {
|
|
L.circle(targetLocation, {
|
|
color: isDarkMode ? '#3b82f6' : '#2563eb',
|
|
fillColor: isDarkMode ? '#3b82f6' : '#2563eb',
|
|
fillOpacity: 0.2,
|
|
radius: toleranceRadius
|
|
}).addTo(map);
|
|
console.log('Added tolerance radius:', toleranceRadius);
|
|
}
|
|
} catch (e) {
|
|
console.error('Error adding tolerance radius:', e);
|
|
}
|
|
})();
|
|
{% endif %}
|
|
});
|
|
</script>
|
|
|
|
{% elif question_data.type != "order" and question_data.type != "guess" %}
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
{% for option in question_data.options %}
|
|
|
|
<div id="option-box-{{ forloop.counter0 }}" class=" dark:bg-transparent relative p-4 rounded-lg overflow-auto break-words hyphens-auto {% if option.is_correct %} border-2 border-green-500{% else %}border-2 border-red-500{% endif %}">
|
|
|
|
<div id="option-fill-{{ forloop.counter0 }}" class="absolute left-0 top-0 h-full {% if option.is_correct %}bg-green-300 {% else %}bg-red-300 {% endif %} opacity-50 z-0 transition-all duration-1500 " style="width: 0%;"></div>
|
|
<div class="relative z-10">
|
|
<p class="text-lg ">{{ option.value }}</p>
|
|
<p class="text-gray-600 dark:text-gray-400" id="option-count-{{ forloop.counter0 }}">0 Antworten</p>
|
|
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% elif question_data.type == "guess" %}
|
|
<div id="option-box-0" class="relative p-4 rounded-lg border-2 border-green-500 overflow-auto">
|
|
<div id="option-fill-0" class="absolute left-0 top-0 h-full bg-green-300 opacity-50 z-0 transition-all duration-1500" style="width: 0%;"></div>
|
|
<div class="relative z-10">
|
|
<p class="text-lg">Richtige Anwort: {% for option in question_data.options%} {{ option.value }}{% endfor %}</p>
|
|
<p class="text-sm">(prozentuale Punktevergabe ab Genauigkeit von {{ question_data.tolerance }}%)</p>
|
|
|
|
<p class="text-gray-600" id="option-count-0">0 Antworten</p>
|
|
</div>
|
|
</div>
|
|
<div id="option-box-1" class="relative p-4 rounded-lg border-2 border-red-500 overflow-auto mt-2">
|
|
<div id="option-fill-1" class="absolute left-0 top-0 h-full bg-red-300 opacity-50 z-0 transition-all duration-1500" style="width: 0%;"></div>
|
|
<div class="relative z-10">
|
|
<p class="text-lg">Schätzung zu ungenau</p>
|
|
<p class="text-gray-600" id="option-count-1">0 Antworten</p>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{% else %}
|
|
<div id="option-box-0" class="relative p-4 rounded-lg border-2 border-green-500 overflow-auto">
|
|
<div id="option-fill-0" class="absolute left-0 top-0 h-full bg-green-300 opacity-50 z-0 transition-all duration-1500" style="width: 0%;"></div>
|
|
<div class="relative z-10">
|
|
<p class="text-lg">Richtige Reihenfolge</p>
|
|
<ul class="">
|
|
{% for option in question_data.options|dictsort:"order" %}
|
|
<li class="text-lg"> {{ forloop.counter }}. <span class="">{{ option.value }}</span> </li>
|
|
{% endfor %}
|
|
</ul>
|
|
<p class="text-gray-600" id="option-count-0">0 Antworten</p>
|
|
</div>
|
|
</div>
|
|
<div id="option-box-1" class="relative p-4 rounded-lg border-2 border-red-500 overflow-auto mt-2">
|
|
<div id="option-fill-1" class="absolute left-0 top-0 h-full bg-red-300 opacity-50 z-0 transition-all duration-1500" style="width: 0%;"></div>
|
|
<div class="relative z-10">
|
|
<p class="text-lg">Falsche Reihenfolge</p>
|
|
<p class="text-gray-600" id="option-count-1">0 Antworten</p>
|
|
</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 %}
|
|
</div>
|
|
|
|
<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 %}border-yellow-500{% else %}border-transparent{% endif %}">
|
|
<div class="flex flex-col items-start gap-1">
|
|
<div class="text-lg 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-2xl 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" 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-sm">+{{ participant.last_score }} Punkte</p>
|
|
|
|
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% if participant == my_participant or request.session.mode == "learn" %}
|
|
{% if participant.last_answer_correct %}
|
|
|
|
<div class="mt-1 flex justify-center border-t-2 border-gray-300 gap-1">
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="pt-2 text-center text-green-500 font-black size-10">
|
|
<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>
|
|
|
|
|
|
<div class="dark:text-white pt-3 text-center text-gray-600 font-bold" id="antwort-richtig">
|
|
Hier erscheint gleich dein Feedback...
|
|
</div>
|
|
|
|
</div>
|
|
{% elif participant.last_answer_correct == False %}
|
|
|
|
|
|
<div class="mt-1 flex justify-center border-t-2 border-gray-300 gap-1">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="pt-2 text-center text-red-500 font-black size-10">
|
|
<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>
|
|
|
|
|
|
<div class="dark:text-white pt-3 text-center text-gray-600 font-bold" id="antwort-falsch">
|
|
Hier erscheint gleich dein Feedback...
|
|
</div>
|
|
<div class="flex justify-center border-t-2 border-gray-500 gap-1">
|
|
|
|
{% endif %}
|
|
{% endif %}
|
|
</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 %}
|
|
{% 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
|
|
|
|
</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 %}
|
|
</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>
|
|
|
|
|
|
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() {
|
|
|
|
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();
|
|
{% if request.session.mode == "learn" %}
|
|
triggerConfetti();
|
|
{% endif %}
|
|
|
|
|
|
{% 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 %}
|