537 lines
19 KiB
HTML
537 lines
19 KiB
HTML
{% extends 'base.html' %}
|
|
{% block content %}
|
|
|
|
{% load static %}
|
|
<script src="{% static 'js/sortablejs/Sortable.min.js' %}"></script>
|
|
|
|
|
|
<div class="container mx-auto px-4">
|
|
<div class="flex justify-end mb-4">
|
|
{% if request.session.mode == "learn" %}
|
|
<button onclick="leaveGame()" class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded">
|
|
Spiel verlassen
|
|
</button>
|
|
{% endif %}
|
|
<button onclick="this.onclick=null; advanceToScores();" class="mx-2 bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">
|
|
überspringen
|
|
</button>
|
|
</div>
|
|
<div id="form_host" class="bg-white rounded-lg shadow-md p-4 mb-6 dark:bg-transparent dark:text-white">
|
|
<div class="flex justify-between mb-2">
|
|
<div class="flex gap-1 text-sm"><p class="text-sm">Frage</p>
|
|
<p class="text-gray-700 font-bold text-sm dark:text-white">{{ question_index|add:1 }}</p>
|
|
<p class="text-sm">von</p>
|
|
<p class="text-gray-700 font-bold text-sm dark:text-white">{{ total_questions }}</p>
|
|
</div>
|
|
<div class="flex gap-1 mb-2"><p class="text-sm">Antworten:</p> <p id="answer-count" class="text-gray-700 font-bold text-sm dark:text-white"></p></div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<h1 class="text-xl font-black mb-2 border-blue-500 border-4 p-4 text-center rounded-lg md:text-2xl lg:text-4xl">{{ question_data.question}}</h1>
|
|
|
|
{% if question_image %}
|
|
<div class="flex justify-center">
|
|
<img class="m-4 max-h-[33vh] w-auto rounded-lg" src="{{ question_image.image.url }}" alt="Bild der Frage">
|
|
</div>
|
|
{% endif %}
|
|
{% if request.session.mode != "learn" %}
|
|
<div class="w-full bg-gray-300 rounded-full h-4">
|
|
<div id="time-bar" class="bg-blue-600 h-4 rounded-full transition-all duration-100 linear" style="width: 100%;"></div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if request.session.mode == "learn" %}
|
|
<div class="grid grid-cols-1 gap-4 mb-6">
|
|
{% else %}
|
|
<div class="grid grid-cols-2 gap-4 mb-6">
|
|
{% endif %}
|
|
|
|
|
|
</div>
|
|
{% if question_data.type == "multiple_choice" or question_data.type == "true_false" %}
|
|
<div class="grid grid-cols-2 gap-4" id="options-container">
|
|
{% for option in question_data.options %}
|
|
<button
|
|
class="
|
|
{% if forloop.counter0 == 0 %}
|
|
bg-green-500
|
|
hover:bg-green-600
|
|
{% elif forloop.counter0 == 1 %}
|
|
bg-red-500
|
|
hover:bg-red-600
|
|
{% elif forloop.counter0 == 2 %}
|
|
bg-blue-500
|
|
hover:bg-blue-600
|
|
{% elif forloop.counter0 == 3 %}
|
|
bg-purple-500
|
|
hover:bg-purple-600
|
|
{% elif forloop.counter0 == 4 %}
|
|
bg-orange-500
|
|
hover:bg-orange-600
|
|
{% elif forloop.counter0 == 5 %}
|
|
bg-yellow-500
|
|
hover:bg-yellow-600
|
|
{% else %}
|
|
bg-gray-500
|
|
hover:bg-gray-600
|
|
{% endif %}
|
|
p-4 rounded-lg text-white transition-colors duration-200 answer-option overflow-auto break-words hyphens-auto "
|
|
data-index="{{ forloop.counter0 }}"
|
|
{% if request.session.mode == "learn" %}
|
|
onclick="submitAnswer({{ forloop.counter0 }});" {% endif %}>
|
|
<p class="text-lg">{{ option.value }}</p>
|
|
</button>
|
|
|
|
{% endfor %}
|
|
</div>
|
|
|
|
|
|
{% elif question_data.type == "order" %}
|
|
|
|
<div id="sortable-options" class=" mb-4 flex flex-col items-center">
|
|
{% for option in question_data.options %}
|
|
|
|
<button data-index="{{ forloop.counter0 }}"
|
|
data-value="{{ option.value }}"
|
|
|
|
class=" my-2 w-3/4 max-w-xl
|
|
{% if forloop.counter0 == 0 %}
|
|
bg-green-500
|
|
hover:bg-green-600
|
|
{% elif forloop.counter0 == 1 %}
|
|
bg-red-500
|
|
hover:bg-red-600
|
|
{% elif forloop.counter0 == 2 %}
|
|
bg-blue-500
|
|
hover:bg-blue-600
|
|
{% elif forloop.counter0 == 3 %}
|
|
bg-purple-500
|
|
hover:bg-purple-600
|
|
{% elif forloop.counter0 == 4 %}
|
|
bg-orange-500
|
|
hover:bg-orange-600
|
|
{% elif forloop.counter0 == 5 %}
|
|
bg-yellow-500
|
|
hover:bg-yellow-600
|
|
{% else %}
|
|
bg-gray-500
|
|
hover:bg-gray-600
|
|
{% endif %}
|
|
p-4 rounded-lg text-white transition-colors duration-200 answer-option overflow-auto break-words hyphens-auto "
|
|
data-index="{{ forloop.counter0 }}"
|
|
|
|
>
|
|
|
|
<p class="text-lg flex items-center">
|
|
<span class="order-number inline-block w-8 h-8 rounded-full bg-white text-gray-800 font-bold text-center leading-8 mr-3">1</span>
|
|
|
|
{{ option.value }}
|
|
</p>
|
|
|
|
</button>
|
|
|
|
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<div class="flex gap-2">
|
|
{% if request.session.mode == "learn" %}
|
|
<button
|
|
onclick="submitOrderAnswer()"
|
|
id="send_order" class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg transition-colors duration-200 answer-option "
|
|
>
|
|
Abschicken
|
|
</button>
|
|
{% endif %}
|
|
|
|
|
|
|
|
{% elif question_data.type == "input" %}
|
|
{% if request.session.mode == "learn" %}
|
|
<form action="">
|
|
<input id="textanswer" type="text" placeholder="DEINE ANTWORT"
|
|
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
|
|
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 "
|
|
>
|
|
Abschicken
|
|
</button>
|
|
</form>
|
|
|
|
{% endif %}
|
|
{% elif question_data.type == "map" and request.session.mode == "learn" %}
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
|
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
|
crossorigin=""></script>
|
|
<div class="text-sm text-gray-600 dark:text-gray-400 mb-2">Ziel auswählen</div>
|
|
<div id="map" class="h-96"></div>
|
|
<input type="hidden" value="" id="singleanswer" name="singleanswer">
|
|
<div class="mt-4">
|
|
<button onclick="submitAnswerMap()" class="bg-blue-400 text-white px-4 py-2 rounded-md hover:bg-blue-500 transition-colors">
|
|
Abgeben
|
|
</button>
|
|
</div>
|
|
<input type="hidden" id="location" name="location" value="">
|
|
<script>
|
|
let marker = null;
|
|
const locationInput = document.getElementById('location');
|
|
const map = L.map('map').setView([51.72, 10.45], 3);
|
|
|
|
// Prüfe, ob Dark Mode aktiv ist
|
|
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
|
|
// Wähle das passende Karten-Design
|
|
const tileUrl = isDarkMode
|
|
? 'https://{s}.basemaps.cartocdn.com/rastertiles/dark_nolabels/{z}/{x}/{y}.png'
|
|
: 'https://{s}.basemaps.cartocdn.com/rastertiles/light_nolabels/{z}/{x}/{y}.png';
|
|
|
|
L.tileLayer(tileUrl, {
|
|
maxZoom: 19,
|
|
attribution: '© OpenStreetMap © CARTO'
|
|
}).addTo(map);
|
|
|
|
function updateInput(latlng) {
|
|
locationInput.value = `${latlng.lat.toFixed(6)},${latlng.lng.toFixed(6)}`;
|
|
}
|
|
|
|
function placeOrMoveMarker(latlng) {
|
|
if (marker) {
|
|
marker.setLatLng(latlng);
|
|
} else {
|
|
marker = L.marker(latlng, { draggable: true }).addTo(map);
|
|
marker.on('dragend', () => {
|
|
updateInput(marker.getLatLng());
|
|
});
|
|
}
|
|
updateInput(latlng);
|
|
}
|
|
|
|
map.on('click', e => placeOrMoveMarker(e.latlng));
|
|
|
|
function submitAnswerMap() {
|
|
const answer = document.getElementById("location").value;
|
|
|
|
if (answer === "") {
|
|
alert("Bitte einen Ort auf der Karte auswählen!");
|
|
return;
|
|
}
|
|
|
|
// Add grey overlay
|
|
const overlay = document.createElement('div');
|
|
overlay.style.position = 'fixed';
|
|
overlay.style.top = '0';
|
|
overlay.style.left = '0';
|
|
overlay.style.width = '100%';
|
|
overlay.style.height = '100%';
|
|
overlay.style.backgroundColor = 'rgba(128, 128, 128, 0.5)';
|
|
overlay.style.zIndex = '1000';
|
|
overlay.style.display = 'flex';
|
|
overlay.style.justifyContent = 'center';
|
|
overlay.style.alignItems = 'center';
|
|
overlay.style.color = 'white';
|
|
overlay.style.fontSize = '24px';
|
|
overlay.style.fontWeight = 'bold';
|
|
overlay.textContent = 'Antwort übermittelt';
|
|
document.body.appendChild(overlay);
|
|
|
|
const now = Date.now();
|
|
const timeRemaining = Math.max(0, questionDuration - (now - questionStartTime));
|
|
|
|
// Submit the answer
|
|
submitAnswer(answer);
|
|
}
|
|
</script>
|
|
{% elif question_data.type == "map" and request.session.mode != "learn" %}
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2 text-center">Bitte den gesuchten Ort auf der Karte auswählen</p>
|
|
{% elif question_data.type == "guess" %}
|
|
{% if request.session.mode == "learn" %}
|
|
<form class="flex flex-col items-center gap-4 mt-4">
|
|
<!-- Slider mit Beschriftung -->
|
|
<div class="w-full max-w-xl text-center">
|
|
<input
|
|
id="textanswer"
|
|
type="range"
|
|
min="{{ question_data.min }}"
|
|
max="{{ question_data.max }}"
|
|
value="{{ question_data.min }}"
|
|
step="1"
|
|
oninput="document.getElementById('range-value').textContent = 'Wert: ' + this.value;"
|
|
class="w-full answer-option bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 dark:bg-transparent dark:text-white dark:hover:bg-[#2a2f3a]"
|
|
>
|
|
<p id="range-value" class="mt-2 font-bold text-gray-800 dark:text-white">Wert: {{ question_data.min }}</p>
|
|
</div>
|
|
|
|
<!-- Abschicken-Button -->
|
|
|
|
<button
|
|
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 "
|
|
>
|
|
Abschicken
|
|
</button>
|
|
|
|
</form>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
|
|
|
|
{% block extra_js %}
|
|
{% include 'play/game/common_scripts.html' %}
|
|
{% include 'play/game/sound.html' %}
|
|
<script>
|
|
const joinCode = '{{ quiz_game.join_code }}';
|
|
const hostId = '{{ host_id }}';
|
|
const questionStartTime = {{ start_time }};
|
|
const questionDuration = '{{ current_question.time_per_question }}'*1000; //Zeit pro Frage (indviduell eingestellt)
|
|
const gameSocket = initializeGameSocket(joinCode);
|
|
let answeredParticipants = 0;
|
|
let totalParticipants = 0;
|
|
const optionCounts = {};
|
|
|
|
gameSocket.onmessage = function(e) {
|
|
const data = JSON.parse(e.data);
|
|
if (data.type === 'participant_answer') {
|
|
answeredParticipants++;
|
|
updateAnswerCount();
|
|
updateOptionCount(data.answer);
|
|
} else if (data.type === 'participant_list_update') {
|
|
totalParticipants = data.participants.length;
|
|
updateAnswerCount();
|
|
} else if (data.type === 'game_state_update' &&
|
|
data.action === 'show_scores' &&
|
|
data.redirect_url) {
|
|
|
|
|
|
setTimeout(() => {
|
|
submitAnswer(-1);
|
|
window.location.href = data.redirect_url;
|
|
}, 1000);
|
|
}
|
|
};
|
|
|
|
gameSocket.onclose = function(e) {
|
|
wsUtil.handleClose(e);
|
|
};
|
|
|
|
function updateAnswerCount() {
|
|
const answerCountElement = document.getElementById('answer-count');
|
|
if (answerCountElement) {
|
|
answerCountElement.textContent = `${answeredParticipants}/${totalParticipants}`;
|
|
}
|
|
|
|
// If everyone has answered, advance to scores
|
|
if (answeredParticipants === totalParticipants && totalParticipants > 0) {
|
|
advanceToScores();
|
|
}
|
|
}
|
|
|
|
function updateOptionCount(optionIndex) {
|
|
optionCounts[optionIndex] = (optionCounts[optionIndex] || 0) + 1;
|
|
const optionElement = document.getElementById(`option-count-${optionIndex}`);
|
|
if (optionElement) {
|
|
optionElement.textContent = `${optionCounts[optionIndex]} Antworten`;
|
|
}
|
|
}
|
|
|
|
|
|
function advanceToScores() {
|
|
// Show correct answers and answer counts
|
|
var correctOptions = document.querySelectorAll('[data-correct="True"]');
|
|
for (var i = 0; i < correctOptions.length; i++) {
|
|
correctOptions[i].classList.add('border-2', 'border-blue-500');
|
|
}
|
|
|
|
// Show answer counts
|
|
var countElements = document.querySelectorAll('[id^="option-count-"]');
|
|
for (var j = 0; j < countElements.length; j++) {
|
|
countElements[j].classList.remove('hidden');
|
|
}
|
|
|
|
// Wait a moment to show the correct answer and counts
|
|
setTimeout(function() {
|
|
gameSocket.send(JSON.stringify({
|
|
'type': 'advance_to_scores',
|
|
'host_id': hostId
|
|
}));
|
|
}, 0);
|
|
}
|
|
|
|
// Timer
|
|
{% if request.session.mode != "learn" %}
|
|
function updateTimer() {
|
|
const now = Date.now();
|
|
const elapsed = now - questionStartTime;
|
|
const remaining = Math.max(0, (questionDuration - elapsed) / 1000);
|
|
|
|
|
|
//document.getElementById('remaining-time').textContent = remaining;
|
|
|
|
document.getElementById('time-bar').style.width = ((remaining * 1000) / questionDuration * 100) + '%';
|
|
if (remaining<3){
|
|
document.getElementById('time-bar').style.backgroundColor="red";}else if(remaining<5){document.getElementById('time-bar').style.backgroundColor="orange";}
|
|
|
|
if (remaining === 0) {
|
|
advanceToScores();
|
|
|
|
} else {
|
|
requestAnimationFrame(updateTimer);
|
|
}
|
|
}
|
|
|
|
|
|
{% endif %}
|
|
|
|
// Request initial participants list
|
|
gameSocket.onopen = function(e) {
|
|
gameSocket.send(JSON.stringify({
|
|
'type': 'update_participants'
|
|
}));
|
|
{% if request.session.mode != "learn" %}
|
|
updateTimer();
|
|
{% endif %}
|
|
};
|
|
|
|
|
|
let hasAnswered = false;
|
|
const participantId = '{{ host_id }}'; // HIER IST DER HOST DER SPIELER
|
|
|
|
function leaveGame() {
|
|
if (confirm('Möchtest du das Spiel wirklich verlassen?')) {
|
|
gameSocket.send(JSON.stringify({
|
|
type: 'leave_game',
|
|
participant_id: participantId
|
|
}));
|
|
window.location.href = '/';
|
|
gameSocket.close();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function submitAnswer(optionIndex) {
|
|
if (hasAnswered) return;
|
|
let input = ""; // Declare input here
|
|
|
|
try {
|
|
input = document.getElementById("textanswer").value;
|
|
} catch (e) {
|
|
input = "";
|
|
}
|
|
hasAnswered = true;
|
|
const now = Date.now();
|
|
let timeRemaining = Math.max(0, questionDuration - (now - questionStartTime));
|
|
{% if request.session.mode == "learn" %}
|
|
timeRemaining = questionDuration;
|
|
{% endif %}
|
|
|
|
|
|
// Disable all options and highlight selected
|
|
const options = document.getElementsByClassName('answer-option');
|
|
for (let option of options) {
|
|
option.disabled = true;
|
|
option.classList.remove('hover:bg-blue-100');
|
|
if (parseInt(option.dataset.index) === optionIndex) {
|
|
option.classList.remove('bg-gray-100');
|
|
option.classList.add('bg-blue-600', 'text-white');
|
|
} else {
|
|
option.classList.add('opacity-50');
|
|
}
|
|
}
|
|
if(input!=""){
|
|
// Send answer to server
|
|
gameSocket.send(JSON.stringify({
|
|
type: 'submit_answer',
|
|
participant_id: participantId,
|
|
answer:input,
|
|
time_remaining: timeRemaining
|
|
}));
|
|
}
|
|
else{
|
|
gameSocket.send(JSON.stringify({
|
|
type: 'submit_answer',
|
|
participant_id: participantId,
|
|
answer:optionIndex,
|
|
time_remaining: timeRemaining
|
|
}));
|
|
}
|
|
}
|
|
let initialOrder = [];
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const container = document.getElementById('sortable-options');
|
|
if (container) {
|
|
initialOrder = Array.from(container.children).map(div => div.dataset.index);
|
|
|
|
Sortable.create(container, {
|
|
animation: 150,
|
|
onUpdate: updateOrderNumbers // <--- Nummern aktualisieren nach Sortieren
|
|
});
|
|
|
|
updateOrderNumbers(); // initial setzen
|
|
}
|
|
});
|
|
|
|
function updateOrderNumbers() {
|
|
const container = document.getElementById('sortable-options');
|
|
const children = container.children;
|
|
for (let i = 0; i < children.length; i++) {
|
|
const label = children[i].querySelector('.order-number');
|
|
if (label) {
|
|
label.textContent = `${i + 1}.`;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function submitOrderAnswer() {
|
|
const order_button=document.getElementById("send_order")
|
|
order_button.disabled=true;
|
|
order_button.classList.add('opacity-50');
|
|
const orderedDivs = document.querySelectorAll('#sortable-options button');
|
|
|
|
const selectedOrder = Array.from(orderedDivs).map(div => div.dataset.value);
|
|
|
|
|
|
const now = Date.now();
|
|
let timeRemaining = Math.max(0, questionDuration - (now - questionStartTime));
|
|
{% if request.session.mode == "learn" %}
|
|
timeRemaining = questionDuration;
|
|
{% endif %}
|
|
|
|
gameSocket.send(JSON.stringify({
|
|
type: 'submit_answer',
|
|
participant_id: '{{ host_id }}',
|
|
answer: selectedOrder,
|
|
time_remaining: timeRemaining
|
|
}));
|
|
|
|
hasAnswered = true;
|
|
}
|
|
|
|
function resetOrderSelection() {
|
|
const container = document.getElementById('sortable-options');
|
|
const divs = Array.from(container.children);
|
|
const indexMap = {};
|
|
divs.forEach(div => {
|
|
indexMap[div.dataset.index] = div;
|
|
});
|
|
|
|
// Sort children back to initial order
|
|
container.innerHTML = '';
|
|
initialOrder.forEach(i => {
|
|
container.appendChild(indexMap[i]);
|
|
});
|
|
}
|
|
|
|
|
|
</script>
|
|
{% endblock %} |