Initial Game Logic 2

This commit is contained in:
Juhn-Vitus Saß
2025-04-06 15:57:42 +02:00
parent 3637edce33
commit 3f07ce4ec7
15 changed files with 1506 additions and 151 deletions

View File

@@ -1,35 +1,129 @@
{% load static %}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'css/t-style.css' %}">
<title>qivip</title>
</head>
<body>
<div>
<div class="qp-question-container">
<p class="text-gray-700 font-bold text-xl uppercase">Frage X</p>
<img src="integral.png">
<h1>Frage: {{ question.data.question }}</h1>
<div>
<div class="p-4">
<p class="text-blue-500 text-xl">Verbleibende Zeit:</p>
<p id="remaining_time" class="text-6xl">36</p>
</div>
<div class="p-4">
<p class="text-blue-500 text-xl">7/14 Antworten</p>
</div>
{% extends 'base.html' %}
{% block content %}
<div class="container mx-auto px-4">
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<p class="text-gray-700 font-bold text-xl mb-4">Frage {{ question_index|add:1 }} von {{ total_questions }}</p>
<h1 class="text-3xl font-bold mb-6">{{ question_data.question }}</h1>
<div class="grid grid-cols-2 gap-4 mb-6">
<div class="p-4 bg-blue-50 rounded-lg">
<p class="text-blue-600 text-xl mb-2">Verbleibende Zeit</p>
<p id="remaining-time" class="text-6xl font-bold text-blue-700">30</p>
</div>
<div class="p-4 bg-blue-50 rounded-lg">
<p class="text-blue-600 text-xl mb-2">Antworten</p>
<p id="answer-count" class="text-6xl font-bold text-blue-700">0/0</p>
</div>
</div>
<div class="qp-answer-container-host" id="qp-answer-container-host">
{% for option in question.data.options %}
<div>
<p>{{ option.value }}</p>
<div class="grid grid-cols-2 gap-4">
{% for option in question_data.options %}
<div class="p-4 bg-gray-100 rounded-lg" data-correct="{{ option.is_correct }}">
<p class="text-lg">{{ option.value }}</p>
<p id="option-count-{{ forloop.counter0 }}" class="text-gray-600 hidden">0 Antworten</p>
</div>
{% endfor %}
</div>
</div>
</body>
</html>
</div>
{% endblock %}
{% block extra_js %}
{% include 'play/game/common_scripts.html' %}
<script>
const joinCode = '{{ quiz_game.join_code }}';
const hostId = '{{ host_id }}';
const questionStartTime = {{ start_time }};
const questionDuration = 30000; // 30 seconds in milliseconds
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) {
window.location.href = data.redirect_url;
}
};
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-green-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
}));
}, 2000);
}
// Timer
function updateTimer() {
const now = Date.now();
const elapsed = now - questionStartTime;
const remaining = Math.max(0, Math.ceil((questionDuration - elapsed) / 1000));
document.getElementById('remaining-time').textContent = remaining;
if (remaining === 0) {
advanceToScores();
} else {
requestAnimationFrame(updateTimer);
}
}
// Request initial participants list
gameSocket.onopen = function(e) {
gameSocket.send(JSON.stringify({
'type': 'update_participants'
}));
updateTimer();
};
</script>
{% endblock %}

View File

@@ -1,19 +1,115 @@
{% load static %}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'css/t-style.css' %}">
<title>qivip</title>
</head>
<body>
<div class="qp-answer-container" id="qp-answer-container">
{% for option in question.data.options %}
<div>
<p>{{ option.value }}</p>
</div>
{% endfor %}
{% extends 'base.html' %}
{% block content %}
<div class="container mx-auto px-4">
<div class="flex justify-end mb-4">
<button onclick="leaveGame()" class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded">
Spiel verlassen
</button>
</div>
</body>
</html>
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<p class="text-gray-700 font-bold text-xl mb-4">Frage {{ question_index|add:1 }} von {{ total_questions }}</p>
<h1 class="text-3xl font-bold mb-6">{{ question_data.question }}</h1>
<div class="p-4 bg-blue-50 rounded-lg mb-6">
<p class="text-blue-600 text-xl mb-2">Verbleibende Zeit</p>
<p id="remaining-time" class="text-6xl font-bold text-blue-700">30</p>
</div>
<div class="grid grid-cols-2 gap-4" id="options-container">
{% for option in question_data.options %}
<button
class="p-4 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option"
data-index="{{ forloop.counter0 }}"
onclick="submitAnswer({{ forloop.counter0 }});">
<p class="text-lg">{{ option.value }}</p>
</button>
{% endfor %}
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
{% include 'play/game/common_scripts.html' %}
<script>
let hasAnswered = false;
const joinCode = '{{ quiz_game.join_code }}';
const participantId = '{{ participant.participant_id }}';
const questionStartTime = {{ start_time }};
const questionDuration = 30000; // 30 seconds in milliseconds
const gameSocket = initializeGameSocket(joinCode);
gameSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
if (data.type === 'redirect' && data.url) {
window.location.href = data.url;
} else if (data.type === 'game_state_update' &&
data.action === 'show_scores' &&
data.redirect_url) {
window.location.href = data.redirect_url;
}
};
gameSocket.onclose = function(e) {
wsUtil.handleClose(e);
};
function submitAnswer(optionIndex) {
if (hasAnswered) return;
hasAnswered = true;
const now = Date.now();
const timeRemaining = Math.max(0, questionDuration - (now - questionStartTime));
// 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');
}
}
// Send answer to server
gameSocket.send(JSON.stringify({
type: 'submit_answer',
participant_id: participantId,
answer: optionIndex,
time_remaining: timeRemaining
}));
}
// Timer
function updateTimer() {
const now = Date.now();
const elapsed = now - questionStartTime;
const remaining = Math.max(0, Math.ceil((questionDuration - elapsed) / 1000));
document.getElementById('remaining-time').textContent = remaining;
if (remaining === 0 && !hasAnswered) {
// Auto-submit timeout answer
submitAnswer(-1);
} else if (remaining > 0) {
requestAnimationFrame(updateTimer);
}
}
function leaveGame() {
if (confirm('Möchtest du das Spiel wirklich verlassen?')) {
gameSocket.send(JSON.stringify({
type: 'leave_game',
participant_id: participantId
}));
}
}
// Start timer when page loads
updateTimer();
</script>
{% endblock %}

View File

@@ -1,5 +1,128 @@
<!-->
Spieler sehen ihre Punktzahl, Platzierung und Richtig/Falsch
{% extends 'base.html' %}
Host: Scoreboard, Button: 'Weiter'
</!-->
{% block content %}
<div class="container mx-auto px-4">
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h1 class="text-3xl font-bold mb-6">Ergebnisse</h1>
<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>
<div class="grid grid-cols-2 gap-4">
{% for option in question_data.options %}
<div class="p-4 rounded-lg {% if option.is_correct %}bg-green-100 border-2 border-green-500{% else %}bg-gray-100{% endif %}">
<p class="text-lg">{{ option.value }}</p>
<p class="text-gray-600" id="option-count-{{ forloop.counter0 }}">0 Antworten</p>
</div>
{% endfor %}
</div>
</div>
<div class="mb-6">
<h2 class="text-xl font-bold mb-4">Punktestand</h2>
<div class="space-y-2">
{% for participant in participants %}
<div class="p-4 {% if forloop.first %}bg-yellow-100 border-2 border-yellow-500{% else %}bg-gray-100{% endif %} rounded-lg flex justify-between items-center">
<div>
<p class="text-lg font-bold">{{ participant.display_name }}</p>
<p class="text-gray-600">{{ participant.score }} Punkte</p>
</div>
{% if participant.last_answer_correct %}
<span class="text-green-500"></span>
{% elif participant.last_answer_correct == False %}
<span class="text-red-500"></span>
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% if is_host %}
{% if is_last_question %}
<button id="finish-button" class="w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
Quiz beenden
</button>
{% else %}
<button id="next-button" class="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 %}
{% include 'play/game/common_scripts.html' %}
<script>
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')) {
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
}));
});
}
{% else %}
var nextButton = document.getElementById('next-button');
if (nextButton) {
nextButton.addEventListener('click', function() {
gameSocket.send(JSON.stringify({
'type': 'next_question',
'host_id': hostId
}));
});
}
{% endif %}
{% endif %}
function updateAnswerStats(stats) {
for (var optionIndex in stats) {
if (stats.hasOwnProperty(optionIndex)) {
var element = document.getElementById('option-count-' + optionIndex);
if (element) {
element.textContent = stats[optionIndex] + ' Antworten';
}
}
}
}
// Request answer stats when page loads
gameSocket.onopen = function(e) {
gameSocket.send(JSON.stringify({
'type': 'get_answer_stats'
}));
};
</script>
{% endblock %}

View File

@@ -1,5 +1,53 @@
<!-->
Spieler: Countdown, automatische Weiterleitung
{% extends 'base.html' %}
(Host: Frage)
</!-->
{% block content %}
<div class="container mx-auto px-4">
<div class="bg-white rounded-lg shadow-md p-6 mb-6 text-center">
<h1 class="text-3xl font-bold mb-6">Warte auf andere Spieler...</h1>
<div class="p-4 bg-blue-50 rounded-lg mb-6 inline-block">
<p class="text-blue-600 text-xl mb-2">Die nächste Frage beginnt in</p>
<p id="countdown" class="text-6xl font-bold text-blue-700">5</p>
</div>
<div class="animate-pulse">
<p class="text-gray-600">Bitte warte, bis alle Spieler bereit sind</p>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
{% include 'play/game/common_scripts.html' %}
<script>
const joinCode = '{{ quiz_game.join_code }}';
const wsScheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
const gameSocket = new WebSocket(
wsScheme + '://' + window.location.host + '/ws/game/' + joinCode + '/'
);
let countdown = 5;
const countdownElement = document.getElementById('countdown');
gameSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
if (data.type === 'game_state_update' && data.action === 'start_question' && data.redirect_url) {
window.location.href = data.redirect_url;
}
};
gameSocket.onclose = function(e) {
wsUtil.handleClose(e);
};
function updateCountdown() {
countdownElement.textContent = countdown;
if (countdown > 0) {
countdown--;
setTimeout(updateCountdown, 1000);
}
}
updateCountdown();
</script>
{% endblock %}