Compare commits
13 Commits
a8b0efb0ff
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f08013ebaf | ||
|
|
8c3dd493bf | ||
|
|
cc07d21138 | ||
|
|
5435901143 | ||
|
|
7cb53a3526 | ||
|
|
0d49cbc974 | ||
|
|
0caaace318 | ||
|
|
01114475a4 | ||
|
|
ec4191eb65 | ||
|
|
f6d080a940 | ||
|
|
c488e5c4bc | ||
|
|
575fcd3f8c | ||
|
|
b30cef5c85 |
27
app.py
27
app.py
@@ -173,6 +173,7 @@ def quiz(playlist_id):
|
||||
session[f'played_tracks_{playlist_id}'] = played_tracks
|
||||
session[f'score_{playlist_id}'] = score
|
||||
session[f'total_played_{playlist_id}'] = total_played
|
||||
session.modified = True # Erzwinge Session-Speicherung
|
||||
|
||||
# Für die Anzeige der beantworteten Fragen (nutze total_played statt len(played_tracks))
|
||||
answered = total_played - 1 if total_played > 0 else 0
|
||||
@@ -196,13 +197,8 @@ def quiz(playlist_id):
|
||||
# Wähle das passende Template
|
||||
if buzzer_mode == '1' and local_multiplayer == '1':
|
||||
template_name = "quiz_buzzer_multiplayer.html"
|
||||
# Initialisiere Spieler-Scores basierend auf gespeicherter Team-Anzahl
|
||||
# Default: 6 Teams (Maximum), Frontend lädt nur die konfigurierten
|
||||
player_scores = session.get(f'player_scores_{playlist_id}', [0, 0, 0, 0, 0, 0])
|
||||
# Stelle sicher, dass wir immer 6 Scores haben
|
||||
while len(player_scores) < 6:
|
||||
player_scores.append(0)
|
||||
session[f'player_scores_{playlist_id}'] = player_scores
|
||||
# Lade Spieler-Scores
|
||||
player_scores = session.get(f'player_scores_{playlist_id}', [0, 0, 0, 0])
|
||||
elif buzzer_mode == '1':
|
||||
template_name = "quiz_buzzer.html"
|
||||
player_scores = None
|
||||
@@ -248,7 +244,7 @@ def playerselect(playlist_id):
|
||||
|
||||
@app.route('/team_setup/<playlist_id>')
|
||||
def team_setup(playlist_id):
|
||||
"""Configure team names and count for multiplayer buzzer mode."""
|
||||
"""Setup team names and count for multiplayer buzzer mode."""
|
||||
game_mode = request.args.get('mode', 'title')
|
||||
user = session.get('user')
|
||||
return render_template('team_setup.html',
|
||||
@@ -466,10 +462,25 @@ def reset_quiz(playlist_id):
|
||||
session.pop(f'played_tracks_{playlist_id}', None)
|
||||
session.pop(f'score_{playlist_id}', None)
|
||||
session.pop(f'total_played_{playlist_id}', None) # Auch den Zähler zurücksetzen
|
||||
session.pop(f'player_scores_{playlist_id}', None) # Multiplayer-Scores zurücksetzen
|
||||
next_mode = request.args.get('next_mode')
|
||||
if next_mode:
|
||||
return redirect(url_for('quiz', playlist_id=playlist_id, mode=next_mode))
|
||||
return redirect(url_for('playlists'))
|
||||
|
||||
@app.route("/reset_scores/<playlist_id>")
|
||||
def reset_scores(playlist_id):
|
||||
"""Reset nur die Scores, aber behalte gespielte Tracks."""
|
||||
session[f'score_{playlist_id}'] = 0
|
||||
session[f'player_scores_{playlist_id}'] = [0, 0, 0, 0]
|
||||
|
||||
# Zurück zum Quiz mit aktuellen Parametern
|
||||
mode = request.args.get('mode', 'title')
|
||||
buzzer = request.args.get('buzzer', '0')
|
||||
local_multiplayer = request.args.get('local_multiplayer', '0')
|
||||
|
||||
return redirect(url_for('quiz', playlist_id=playlist_id, mode=mode, buzzer=buzzer, local_multiplayer=local_multiplayer))
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000, debug=True)
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<form method="get" action="{{ url_for('team_setup', playlist_id=playlist_id) }}">
|
||||
<input type="hidden" name="mode" value="{{ game_mode }}">
|
||||
<button class="player-btn" type="submit">{{ translations['local_multiplayer'] }}</button>
|
||||
<div class="player-desc">Spiele mit 2-6 Teams an einem Gerät.</div>
|
||||
<div class="player-desc">Spiele mit bis zu 4 Personen an einem Gerät.</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="get" action="{{ url_for('quiz', playlist_id=playlist_id) }}">
|
||||
|
||||
@@ -570,6 +570,7 @@ window.onload = function() {
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="controls">
|
||||
<a href="/reset_scores/{{ playlist_id }}?mode={{ game_mode }}" class="btn" style="background:linear-gradient(135deg, #FFA500 0%, #FF8C00 100%); box-shadow: 0 4px 15px rgba(255, 165, 0, 0.3);" onclick="return confirm('Punkte zurücksetzen?')">🔄 Punkte zurücksetzen</a>
|
||||
<a href="/reset_quiz/{{ playlist_id }}" class="btn btn-danger">{{ translations['end_quiz'] if translations.get('end_quiz') else '🏁 End Quiz' }}</a>
|
||||
<a href="/playlists" class="btn btn-secondary">{{ translations['back_to_playlists'] if translations.get('back_to_playlists') else '⬅️ Back to Playlists' }}</a>
|
||||
</div>
|
||||
|
||||
@@ -565,6 +565,7 @@
|
||||
|
||||
<!-- Navigation -->
|
||||
<div style="text-align:center; margin-top:30px;">
|
||||
<a href="/reset_scores/{{ playlist_id }}?mode={{ game_mode }}&buzzer=1" class="btn" style="background:linear-gradient(135deg, #FFA500 0%, #FF8C00 100%);" onclick="return confirm('Punkte zurücksetzen?')">🔄 Punkte zurücksetzen</a>
|
||||
<a href="/reset_quiz/{{ playlist_id }}?buzzer=1" class="btn btn-danger">{{ translations['end_quiz'] if translations.get('end_quiz') else '🏁 End Quiz' }}</a>
|
||||
<a href="/playlists" class="btn btn-secondary">{{ translations['back_to_playlists'] if translations.get('back_to_playlists') else '⬅️ Back' }}</a>
|
||||
</div>
|
||||
|
||||
@@ -97,36 +97,30 @@
|
||||
color: #f44336;
|
||||
}
|
||||
.buzzer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 15px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.buzzer-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.buzzer-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin: 20px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.player-buzzer {
|
||||
height: 150px;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
|
||||
border: 5px solid rgba(244, 67, 54, 0.3);
|
||||
font-size: 1.5em;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
|
||||
color: white;
|
||||
border: 3px solid rgba(244, 67, 54, 0.5);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 10px 40px rgba(244, 67, 54, 0.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
.player-buzzer:hover:not(.disabled) {
|
||||
transform: scale(1.05);
|
||||
@@ -142,11 +136,11 @@
|
||||
opacity: 0.5;
|
||||
}
|
||||
.player-buzzer.start {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: linear-gradient(135deg, #1DB954 0%, #1ed760 100%);
|
||||
border-color: rgba(29, 185, 84, 0.3);
|
||||
box-shadow: 0 10px 40px rgba(29, 185, 84, 0.5);
|
||||
width: 100%;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.player-buzzer.start:hover {
|
||||
box-shadow: 0 15px 50px rgba(29, 185, 84, 0.7);
|
||||
@@ -254,26 +248,61 @@
|
||||
let decayRate = parseInt(localStorage.getItem('buzzer_decay_rate')) || 50;
|
||||
let gameStarted = false;
|
||||
let currentBuzzer = null;
|
||||
let buzzedPlayers = []; // Liste der Spieler die schon gebuzzert haben
|
||||
let canBuzz = false; // Kann man buzzern?
|
||||
let pendingPoints = 0; // Punkte die noch nicht gezählt wurden
|
||||
|
||||
// Initialisierung verschoben nach unten
|
||||
let teamCount = 0;
|
||||
let players = [];
|
||||
let playerScoresData = {{ player_scores|tojson }} || [0, 0, 0, 0, 0, 0];
|
||||
// Lade Team-Namen und Anzahl
|
||||
const teamCount = parseInt(localStorage.getItem('team_count')) || 4;
|
||||
const teamNames = JSON.parse(localStorage.getItem('team_names') || '["Spieler 1", "Spieler 2", "Spieler 3", "Spieler 4"]');
|
||||
const buzzerMode = localStorage.getItem('buzzer_mode') || 'central';
|
||||
const keyMappings = JSON.parse(localStorage.getItem('key_mappings') || '{"1":"Q","2":"W","3":"E","4":"R"}');
|
||||
|
||||
function initializePlayers() {
|
||||
teamCount = parseInt(localStorage.getItem('team_count')) || 4;
|
||||
players = [];
|
||||
// Spieler-Daten
|
||||
let players = [
|
||||
{ id: 1, name: teamNames[0] || 'Spieler 1', score: {{ player_scores[0] if player_scores else 0 }} },
|
||||
{ id: 2, name: teamNames[1] || 'Spieler 2', score: {{ player_scores[1] if player_scores else 0 }} },
|
||||
{ id: 3, name: teamNames[2] || 'Spieler 3', score: {{ player_scores[2] if player_scores else 0 }} },
|
||||
{ id: 4, name: teamNames[3] || 'Spieler 4', score: {{ player_scores[3] if player_scores else 0 }} }
|
||||
];
|
||||
|
||||
for (let i = 1; i <= teamCount; i++) {
|
||||
const teamName = localStorage.getItem(`team_${i}_name`) || `Team ${i}`;
|
||||
const scoreIndex = i - 1;
|
||||
const score = playerScoresData[scoreIndex] || 0;
|
||||
players.push({ id: i, name: teamName, score: score });
|
||||
// Tastatur-Events für beide Modi
|
||||
document.addEventListener('keydown', function(e) {
|
||||
const active = document.activeElement;
|
||||
const isInput = active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA');
|
||||
if (isInput) return; // Keine Aktionen in Eingabefeldern
|
||||
|
||||
const key = e.key.toUpperCase();
|
||||
|
||||
if (!gameStarted) {
|
||||
// Im persönlichen Modus: Nur die erste Spieler-Taste startet
|
||||
if (buzzerMode === 'personal' && key === keyMappings['1']) {
|
||||
e.preventDefault();
|
||||
startGame();
|
||||
}
|
||||
// Im zentralen Modus: Leertaste startet
|
||||
else if (buzzerMode === 'central' && e.code === 'Space') {
|
||||
e.preventDefault();
|
||||
startGame();
|
||||
}
|
||||
} else if (canBuzz && !currentBuzzer) {
|
||||
// Persönlicher Modus: Jede Spieler-Taste buzzert direkt
|
||||
if (buzzerMode === 'personal') {
|
||||
for (let i = 1; i <= teamCount; i++) {
|
||||
if (key === keyMappings[i.toString()] && !buzzedPlayers.includes(i)) {
|
||||
e.preventDefault();
|
||||
triggerBuzzPersonal(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Zentraler Modus: Leertaste -> Spielerauswahl
|
||||
else if (buzzerMode === 'central' && e.code === 'Space') {
|
||||
e.preventDefault();
|
||||
triggerBuzz();
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Loaded teams:', players);
|
||||
console.log('Team count:', teamCount);
|
||||
}
|
||||
});
|
||||
|
||||
window.onSpotifyWebPlaybackSDKReady = () => {
|
||||
const token = '{{ access_token }}';
|
||||
@@ -292,13 +321,9 @@
|
||||
console.log('Ready with Device ID', device_id);
|
||||
document.getElementById('device_id').value = device_id;
|
||||
window.deviceId = device_id;
|
||||
|
||||
setCorrectAnswer();
|
||||
|
||||
// Aktualisiere Scoreboard falls schon initialisiert
|
||||
if (players.length > 0) {
|
||||
updateScoreboard();
|
||||
}
|
||||
initializePlayers();
|
||||
updateScoreboard();
|
||||
});
|
||||
|
||||
player.addListener('player_state_changed', state => {
|
||||
@@ -320,6 +345,40 @@
|
||||
window.spotifyPlayer = player;
|
||||
};
|
||||
|
||||
function initializePlayers() {
|
||||
// Update Spieler-Namen und verstecke inaktive Spieler
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const card = document.getElementById(`player${i}`);
|
||||
const selectBtn = document.getElementById(`selectPlayer${i}`);
|
||||
|
||||
if (i <= teamCount) {
|
||||
card.querySelector('.player-name').textContent = players[i - 1].name;
|
||||
card.style.display = 'block';
|
||||
if (selectBtn) {
|
||||
selectBtn.textContent = players[i - 1].name;
|
||||
selectBtn.style.display = 'block';
|
||||
}
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
if (selectBtn) {
|
||||
selectBtn.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update Buzzer-Hint basierend auf Modus
|
||||
const hintElement = document.getElementById('buzzerHint');
|
||||
if (buzzerMode === 'personal') {
|
||||
let hintText = '⌨️ Buzzer-Tasten: ';
|
||||
for (let i = 1; i <= teamCount; i++) {
|
||||
hintText += `${players[i-1].name}=[${keyMappings[i.toString()]}] `;
|
||||
}
|
||||
hintElement.innerHTML = hintText;
|
||||
} else {
|
||||
hintElement.innerHTML = '⌨️ Drücke LEERTASTE zum Buzzern!';
|
||||
}
|
||||
}
|
||||
|
||||
function updateScoreboard() {
|
||||
players.forEach((player, index) => {
|
||||
const card = document.getElementById(`player${player.id}`);
|
||||
@@ -331,11 +390,14 @@
|
||||
|
||||
function startGame() {
|
||||
if (gameStarted) return;
|
||||
|
||||
gameStarted = true;
|
||||
document.getElementById('startButton').classList.add('disabled');
|
||||
document.getElementById('startButton').style.cursor = 'wait';
|
||||
|
||||
var startBtn = document.getElementById('startButton');
|
||||
if (startBtn) {
|
||||
startBtn.classList.add('disabled');
|
||||
startBtn.style.cursor = 'wait';
|
||||
startBtn.style.display = 'none';
|
||||
}
|
||||
document.getElementById('buzzerHint').style.display = 'block';
|
||||
const device_id = window.deviceId;
|
||||
const startPosition = getOption('startPosition', 'start');
|
||||
let position_ms = 0;
|
||||
@@ -343,27 +405,24 @@
|
||||
const duration = {{ track.duration_ms if track.duration_ms else 180000 }};
|
||||
position_ms = Math.floor(Math.random() * (duration - 30000));
|
||||
}
|
||||
|
||||
fetch(`/play_track?device_id=${device_id}&track_uri={{ track.uri }}&position_ms=${position_ms}`, { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(() => {
|
||||
console.log('Play command sent');
|
||||
document.getElementById('startButton').style.display = 'none';
|
||||
// Aktiviere alle Buzzer
|
||||
players.forEach(player => {
|
||||
document.getElementById(`buzzer${player.id}`).classList.remove('disabled');
|
||||
});
|
||||
canBuzz = true;
|
||||
})
|
||||
.catch(error => console.error('Error starting playback:', error));
|
||||
}
|
||||
|
||||
function startBuzzerTimer() {
|
||||
startTime = Date.now();
|
||||
canBuzz = true;
|
||||
updateTimer();
|
||||
}
|
||||
|
||||
function updateTimer() {
|
||||
if (currentBuzzer) return;
|
||||
// Stoppe Timer wenn nicht mehr gebuzzert werden kann (während Spielerauswahl oder Antwort)
|
||||
if (!canBuzz) return;
|
||||
|
||||
const elapsed = (Date.now() - startTime) / 1000;
|
||||
const timerDisplay = document.getElementById('buzzerTimer');
|
||||
@@ -399,31 +458,101 @@
|
||||
return points;
|
||||
}
|
||||
|
||||
function buzz(playerId) {
|
||||
function triggerBuzz() {
|
||||
if (currentBuzzer !== null) return;
|
||||
if (!gameStarted || !startTime) return;
|
||||
if (!canBuzz) return;
|
||||
|
||||
currentBuzzer = playerId;
|
||||
canBuzz = false; // Verhindere weitere Buzzes
|
||||
buzzTime = Date.now();
|
||||
const elapsed = (buzzTime - startTime) / 1000;
|
||||
|
||||
// Stoppe Timer-Updates
|
||||
cancelAnimationFrame(buzzTimer);
|
||||
|
||||
// Friere Timer-Anzeige ein
|
||||
const timerDisplay = document.getElementById('buzzerTimer');
|
||||
const pointsDisplay = document.getElementById('pointsDisplay');
|
||||
timerDisplay.textContent = elapsed.toFixed(2) + 's';
|
||||
const points = calculatePoints(elapsed);
|
||||
pointsDisplay.textContent = points + ' Punkte';
|
||||
|
||||
// Speichere die Zeit beim Buzzern für spätere Berechnung
|
||||
window.pausedAt = Date.now();
|
||||
|
||||
if (window.spotifyPlayer) {
|
||||
window.spotifyPlayer.pause();
|
||||
}
|
||||
|
||||
// Zeige Spielerauswahl
|
||||
document.getElementById('playerSelection').style.display = 'block';
|
||||
document.getElementById('currentPlayer').textContent = 'Wer hat gebuzzert?';
|
||||
pendingPoints = points;
|
||||
}
|
||||
|
||||
function triggerBuzzPersonal(playerId) {
|
||||
if (currentBuzzer !== null) return;
|
||||
if (!canBuzz) return;
|
||||
if (buzzedPlayers.includes(playerId)) {
|
||||
alert(`${players[playerId - 1].name} hat bereits gebuzzert!`);
|
||||
return;
|
||||
}
|
||||
|
||||
canBuzz = false; // Verhindere weitere Buzzes
|
||||
buzzTime = Date.now();
|
||||
const elapsed = (buzzTime - startTime) / 1000;
|
||||
|
||||
// Stoppe Timer-Updates
|
||||
cancelAnimationFrame(buzzTimer);
|
||||
|
||||
// Friere Timer-Anzeige ein
|
||||
const timerDisplay = document.getElementById('buzzerTimer');
|
||||
const pointsDisplay = document.getElementById('pointsDisplay');
|
||||
timerDisplay.textContent = elapsed.toFixed(2) + 's';
|
||||
const points = calculatePoints(elapsed);
|
||||
pointsDisplay.textContent = points + ' Punkte';
|
||||
|
||||
// Speichere die Zeit beim Buzzern für spätere Berechnung
|
||||
window.pausedAt = Date.now();
|
||||
|
||||
if (window.spotifyPlayer) {
|
||||
window.spotifyPlayer.pause();
|
||||
}
|
||||
|
||||
// Direkt Spieler setzen (keine Auswahl nötig)
|
||||
currentBuzzer = playerId;
|
||||
buzzedPlayers.push(playerId);
|
||||
pendingPoints = points;
|
||||
|
||||
// Markiere Spieler
|
||||
document.getElementById(`player${playerId}`).classList.add('buzzed');
|
||||
document.getElementById('currentPlayer').textContent = `${players[playerId - 1].name} hat gebuzzert!`;
|
||||
|
||||
// Deaktiviere alle Buzzer
|
||||
players.forEach(player => {
|
||||
document.getElementById(`buzzer${player.id}`).classList.add('disabled');
|
||||
});
|
||||
document.getElementById('currentPlayer').textContent = `${players[playerId - 1].name} antwortet...`;
|
||||
|
||||
// Zeige Antwortfeld direkt (keine Spielerauswahl)
|
||||
document.getElementById('answerSection').classList.add('active');
|
||||
window.earnedPoints = calculatePoints(elapsed);
|
||||
window.earnedPoints = pendingPoints;
|
||||
}
|
||||
|
||||
function selectPlayer(playerId) {
|
||||
if (buzzedPlayers.includes(playerId)) {
|
||||
alert('Dieser Spieler hat bereits gebuzzert!');
|
||||
return;
|
||||
}
|
||||
|
||||
currentBuzzer = playerId;
|
||||
buzzedPlayers.push(playerId);
|
||||
|
||||
// Markiere Spieler
|
||||
document.getElementById(`player${playerId}`).classList.add('buzzed');
|
||||
document.getElementById('currentPlayer').textContent = `${players[playerId - 1].name} antwortet...`;
|
||||
|
||||
// Verstecke Spielerauswahl, zeige Antwortfeld
|
||||
document.getElementById('playerSelection').style.display = 'none';
|
||||
document.getElementById('answerSection').classList.add('active');
|
||||
window.earnedPoints = pendingPoints;
|
||||
}
|
||||
|
||||
function buzz(playerId) {
|
||||
selectPlayer(playerId);
|
||||
}
|
||||
|
||||
function setCorrectAnswer() {
|
||||
@@ -491,6 +620,10 @@
|
||||
const guess = document.getElementById('answerInput').value;
|
||||
if (!guess) return;
|
||||
|
||||
// Disable input während Prüfung
|
||||
document.getElementById('answerInput').disabled = true;
|
||||
document.querySelector('button[onclick="checkAnswer()"]').disabled = true;
|
||||
|
||||
fetch('/check_answer_buzzer', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -500,7 +633,8 @@
|
||||
game_mode: currentGameMode,
|
||||
playlist_id: '{{ playlist_id }}',
|
||||
earned_points: window.earnedPoints,
|
||||
player_id: currentBuzzer
|
||||
player_id: currentBuzzer,
|
||||
apply_points: false // Punkte NICHT sofort anwenden
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
@@ -508,45 +642,99 @@
|
||||
const resultContainer = document.getElementById('resultContainer');
|
||||
|
||||
if (data.correct) {
|
||||
players[currentBuzzer - 1].score += window.earnedPoints;
|
||||
updateScoreboard();
|
||||
// Speichere Punkte, aber wende sie noch NICHT an
|
||||
pendingPoints = window.earnedPoints;
|
||||
|
||||
resultContainer.innerHTML = `
|
||||
<div style="background:linear-gradient(135deg, rgba(76, 175, 80, 0.2) 0%, rgba(56, 142, 60, 0.2) 100%); padding:20px; border-radius:12px; border:2px solid #4CAF50;">
|
||||
<h3 style="color:#4CAF50; margin-bottom:10px;">✅ ${i18n.correct || 'Correct'}</h3>
|
||||
<h3 style="color:#4CAF50; margin-bottom:10px;">✅ ${i18n.correct || 'Richtig'}</h3>
|
||||
<p style="font-size:1.5em; margin:10px 0;"><strong style="color:#1DB954;">+${window.earnedPoints}</strong> Punkte für ${players[currentBuzzer - 1].name}!</p>
|
||||
<div style="margin-top:20px; padding:20px; background:rgba(15,20,25,0.8); border-radius:12px;">
|
||||
<h4 style="color:#1DB954; margin-bottom:15px;">Song Info:</h4>
|
||||
<p style="margin:8px 0;"><strong style="color:#1DB954;">Titel:</strong> <span style="color:#e0e0e0;">{{ track.name | clean }}</span></p>
|
||||
<p style="margin:8px 0;"><strong style="color:#1DB954;">Künstler:</strong> <span style="color:#e0e0e0;">{{ track.artists[0].name | clean }}</span></p>
|
||||
<p style="margin:8px 0;"><strong style="color:#1DB954;">Album:</strong> <span style="color:#e0e0e0;">{{ track.album.name | clean }}</span></p>
|
||||
<a href="{{ track.external_urls.spotify }}" target="_blank" style="display:inline-block; margin-top:10px; padding:8px 16px; background:linear-gradient(135deg, #1DB954 0%, #1ed760 100%); color:#fff; text-decoration:none; border-radius:20px; font-weight:600;">Auf Spotify öffnen</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('nextQuestionBtn').style.display = 'inline-block';
|
||||
document.getElementById('nextQuestionBtn').onclick = function() {
|
||||
// Wende Punkte beim Klick auf "Nächster Song" an
|
||||
players[currentBuzzer - 1].score += pendingPoints;
|
||||
updateScoreboard();
|
||||
window.location.href = '/quiz/{{ playlist_id }}?mode={{ game_mode }}&buzzer=1&local_multiplayer=1';
|
||||
};
|
||||
} else {
|
||||
// Minuspunkte: 25% der möglichen Punkte
|
||||
const minusPoints = Math.floor(window.earnedPoints * 0.25);
|
||||
pendingPoints = -minusPoints;
|
||||
|
||||
resultContainer.innerHTML = `
|
||||
<div style="background:linear-gradient(135deg, rgba(244, 67, 54, 0.2) 0%, rgba(211, 47, 47, 0.2) 100%); padding:20px; border-radius:12px; border:2px solid #f44336;">
|
||||
<h3 style="color:#f44336; margin-bottom:10px;">❌ ${i18n.incorrect || 'Incorrect'}</h3>
|
||||
<p style="font-size:1.1em; margin:10px 0;">${i18n.your_answer || 'Your answer'}: <strong style="color:#f44336;">${guess}</strong></p>
|
||||
<p style="font-size:1.1em; margin:10px 0;">${i18n.correct_answer || 'Correct answer'}: <strong style="color:#4CAF50;">${data.correct_answer}</strong></p>
|
||||
<h3 style="color:#f44336; margin-bottom:10px;">❌ Falsch</h3>
|
||||
<p style="font-size:1.1em; margin:10px 0;">Deine Antwort: <strong style="color:#f44336;">${guess}</strong></p>
|
||||
<p style="font-size:1.1em; margin:10px 0;"><strong style="color:#f44336;">-${minusPoints}</strong> Punkte für ${players[currentBuzzer - 1].name}</p>
|
||||
<div style="margin-top:20px;">
|
||||
<button class="btn" onclick="continueGame()" style="background:linear-gradient(135deg, #FFA500 0%, #FF8C00 100%);">🔄 Weiterbuzzern</button>
|
||||
<button class="btn" onclick="showSolution()" style="background:linear-gradient(135deg, #1DB954 0%, #1ed760 100%);">📝 Lösung anzeigen</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (data.comparison) {
|
||||
resultContainer.innerHTML += `
|
||||
<div style="margin-top:20px; padding:20px; background:rgba(15,20,25,0.8); border-radius:12px;">
|
||||
<h4 style="color:#1DB954; margin-bottom:15px;">${i18n.song_info || 'Song Info'}:</h4>
|
||||
<p style="margin:8px 0;"><strong style="color:#1DB954;">${i18n.title || 'Title'}:</strong> <span style="color:#e0e0e0;">{{ track.name | clean }}</span></p>
|
||||
<p style="margin:8px 0;"><strong style="color:#1DB954;">${i18n.artist || 'Artist'}:</strong> <span style="color:#e0e0e0;">{{ track.artists[0].name | clean }}</span></p>
|
||||
<p style="margin:8px 0;"><strong style="color:#1DB954;">${i18n.album || 'Album'}:</strong> <span style="color:#e0e0e0;">{{ track.album.name | clean }}</span></p>
|
||||
<p style="margin:8px 0;"><strong style="color:#1DB954;">${i18n.year || 'Year'}:</strong> <span style="color:#e0e0e0;">{{ track.album.release_date[:4] }}</span></p>
|
||||
<a href="{{ track.external_urls.spotify }}" target="_blank" style="display:inline-block; margin-top:10px; padding:8px 16px; background:linear-gradient(135deg, #1DB954 0%, #1ed760 100%); color:#fff; text-decoration:none; border-radius:20px; font-weight:600; transition:all 0.3s ease;">${i18n.open_on_spotify || 'Open on Spotify'}</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
document.getElementById('nextQuestionBtn').style.display = 'inline-block';
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error checking answer:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function continueGame() {
|
||||
// Wende Minuspunkte an
|
||||
players[currentBuzzer - 1].score += pendingPoints;
|
||||
updateScoreboard();
|
||||
|
||||
// Reset für nächsten Buzzer
|
||||
document.getElementById('answerSection').classList.remove('active');
|
||||
document.getElementById('answerInput').value = '';
|
||||
document.getElementById('answerInput').disabled = false;
|
||||
document.querySelector('button[onclick="checkAnswer()"]').disabled = false;
|
||||
document.getElementById('resultContainer').innerHTML = '';
|
||||
document.getElementById(`player${currentBuzzer}`).classList.remove('buzzed');
|
||||
|
||||
currentBuzzer = null;
|
||||
canBuzz = true;
|
||||
|
||||
// Berechne Pausenzeit und passe startTime an
|
||||
const pauseDuration = Date.now() - window.pausedAt;
|
||||
startTime += pauseDuration;
|
||||
|
||||
// Musik weiterspielen
|
||||
if (window.spotifyPlayer) {
|
||||
window.spotifyPlayer.resume();
|
||||
}
|
||||
|
||||
// Timer weiterlaufen lassen (von der pausierten Zeit)
|
||||
buzzTimer = requestAnimationFrame(updateTimer);
|
||||
}
|
||||
|
||||
function showSolution() {
|
||||
// Wende Minuspunkte an
|
||||
players[currentBuzzer - 1].score += pendingPoints;
|
||||
updateScoreboard();
|
||||
|
||||
const resultContainer = document.getElementById('resultContainer');
|
||||
resultContainer.innerHTML = `
|
||||
<div style="margin-top:20px; padding:20px; background:rgba(15,20,25,0.8); border-radius:12px; border:2px solid #1DB954;">
|
||||
<h4 style="color:#1DB954; margin-bottom:15px;">Lösung:</h4>
|
||||
<p style="margin:8px 0;"><strong style="color:#1DB954;">Titel:</strong> <span style="color:#e0e0e0;">{{ track.name | clean }}</span></p>
|
||||
<p style="margin:8px 0;"><strong style="color:#1DB954;">Künstler:</strong> <span style="color:#e0e0e0;">{{ track.artists[0].name | clean }}</span></p>
|
||||
<p style="margin:8px 0;"><strong style="color:#1DB954;">Album:</strong> <span style="color:#e0e0e0;">{{ track.album.name | clean }}</span></p>
|
||||
<a href="{{ track.external_urls.spotify }}" target="_blank" style="display:inline-block; margin-top:10px; padding:8px 16px; background:linear-gradient(135deg, #1DB954 0%, #1ed760 100%); color:#fff; text-decoration:none; border-radius:20px; font-weight:600;">Auf Spotify öffnen</a>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('nextQuestionBtn').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function switchGameMode(mode) {
|
||||
window.location.href = `/reset_quiz/{{ playlist_id }}?next_mode=${mode}&buzzer=1&local_multiplayer=1`;
|
||||
}
|
||||
@@ -556,77 +744,43 @@
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
console.log('Page loaded, initializing...');
|
||||
document.getElementById('startPosition').value = getOption('startPosition', 'start');
|
||||
maxPoints = parseInt(localStorage.getItem('buzzer_max_points')) || 1000;
|
||||
gracePeriod = parseInt(localStorage.getItem('buzzer_grace_period')) || 5;
|
||||
decayRate = parseInt(localStorage.getItem('buzzer_decay_rate')) || 50;
|
||||
document.getElementById('pointsDisplay').textContent = maxPoints + ' Punkte';
|
||||
|
||||
// Initialisiere Spieler
|
||||
initializePlayers();
|
||||
|
||||
// Dynamisches Scoreboard erstellen
|
||||
createScoreboard();
|
||||
// Dynamische Buzzer-Buttons erstellen
|
||||
createBuzzerButtons();
|
||||
|
||||
console.log('Initialization complete');
|
||||
// Update Starttext basierend auf Modus
|
||||
const currentPlayerText = document.getElementById('currentPlayer');
|
||||
if (buzzerMode === 'personal') {
|
||||
currentPlayerText.textContent = `Drücke [${keyMappings['1']}] zum Starten`;
|
||||
} else {
|
||||
currentPlayerText.textContent = 'Drücke LEERTASTE zum Starten';
|
||||
}
|
||||
};
|
||||
|
||||
function createScoreboard() {
|
||||
console.log('Creating scoreboard for', players.length, 'players');
|
||||
const scoreboard = document.getElementById('scoreboard');
|
||||
if (!scoreboard) {
|
||||
console.error('Scoreboard element not found!');
|
||||
return;
|
||||
}
|
||||
scoreboard.innerHTML = '';
|
||||
|
||||
players.forEach(player => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'player-card';
|
||||
card.id = `player${player.id}`;
|
||||
card.innerHTML = `
|
||||
<div class="player-name">${player.name}</div>
|
||||
<div class="player-score">${player.score}</div>
|
||||
`;
|
||||
scoreboard.appendChild(card);
|
||||
});
|
||||
console.log('Scoreboard created');
|
||||
}
|
||||
|
||||
function createBuzzerButtons() {
|
||||
console.log('Creating buzzer buttons for', players.length, 'players');
|
||||
const grid = document.getElementById('buzzerGrid');
|
||||
if (!grid) {
|
||||
console.error('Buzzer grid element not found!');
|
||||
return;
|
||||
}
|
||||
grid.innerHTML = `
|
||||
<button class="player-buzzer start" id="startButton" onclick="startGame()">
|
||||
▶️<br>START
|
||||
</button>
|
||||
`;
|
||||
|
||||
players.forEach(player => {
|
||||
const button = document.createElement('button');
|
||||
button.className = 'player-buzzer disabled';
|
||||
button.id = `buzzer${player.id}`;
|
||||
button.onclick = () => buzz(player.id);
|
||||
button.innerHTML = `🔴<br>${player.name}`;
|
||||
grid.appendChild(button);
|
||||
});
|
||||
console.log('Buzzer buttons created');
|
||||
}
|
||||
</script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<div class="quiz-container">
|
||||
<!-- Scoreboard -->
|
||||
<div class="scoreboard" id="scoreboard">
|
||||
<!-- Dynamisch generiert via JavaScript -->
|
||||
<div class="scoreboard">
|
||||
<div class="player-card" id="player1">
|
||||
<div class="player-name">Spieler 1</div>
|
||||
<div class="player-score">0</div>
|
||||
</div>
|
||||
<div class="player-card" id="player2">
|
||||
<div class="player-name">Spieler 2</div>
|
||||
<div class="player-score">0</div>
|
||||
</div>
|
||||
<div class="player-card" id="player3">
|
||||
<div class="player-name">Spieler 3</div>
|
||||
<div class="player-score">0</div>
|
||||
</div>
|
||||
<div class="player-card" id="player4">
|
||||
<div class="player-name">Spieler 4</div>
|
||||
<div class="player-score">0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="device_id" value="">
|
||||
@@ -639,9 +793,27 @@
|
||||
|
||||
<div class="current-player" id="currentPlayer">Drücke START um zu beginnen</div>
|
||||
|
||||
<!-- Buzzer Buttons -->
|
||||
<div class="buzzer-grid" id="buzzerGrid">
|
||||
<!-- Dynamisch generiert via JavaScript -->
|
||||
<!-- Buzzer Hint -->
|
||||
<div id="buzzerHint" style="text-align:center; font-size:1.5em; color:#1DB954; margin:20px 0; display:none;">
|
||||
⌨️ Drücke LEERTASTE zum Buzzern!
|
||||
</div>
|
||||
|
||||
<!-- Player Selection (versteckt bis gebuzzert) -->
|
||||
<div id="playerSelection" style="display:none; text-align:center; margin:30px 0;">
|
||||
<h3 style="margin-bottom:20px; color:#1DB954;">Wer hat gebuzzert?</h3>
|
||||
<div class="buzzer-grid">
|
||||
<button class="player-buzzer" id="selectPlayer1" onclick="selectPlayer(1)">Spieler 1</button>
|
||||
<button class="player-buzzer" id="selectPlayer2" onclick="selectPlayer(2)">Spieler 2</button>
|
||||
<button class="player-buzzer" id="selectPlayer3" onclick="selectPlayer(3)">Spieler 3</button>
|
||||
<button class="player-buzzer" id="selectPlayer4" onclick="selectPlayer(4)">Spieler 4</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- START Button -->
|
||||
<div style="text-align:center; margin:30px 0;">
|
||||
<button class="player-buzzer start" id="startButton" onclick="startGame()">
|
||||
▶️<br>START
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Answer Section -->
|
||||
@@ -665,6 +837,7 @@
|
||||
|
||||
<!-- Navigation -->
|
||||
<div style="text-align:center; margin-top:30px;">
|
||||
<a href="/reset_scores/{{ playlist_id }}?mode={{ game_mode }}&buzzer=1&local_multiplayer=1" class="btn" style="background:linear-gradient(135deg, #FFA500 0%, #FF8C00 100%);" onclick="return confirm('Alle Punkte zurücksetzen?')">🔄 Punkte zurücksetzen</a>
|
||||
<a href="/reset_quiz/{{ playlist_id }}?buzzer=1&local_multiplayer=1" class="btn btn-danger">{{ translations['end_quiz'] if translations.get('end_quiz') else '🏁 End Quiz' }}</a>
|
||||
<a href="/playlists" class="btn btn-secondary">{{ translations['back_to_playlists'] if translations.get('back_to_playlists') else '⬅️ Back' }}</a>
|
||||
</div>
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
<span id="multiplayerPlayers"></span>
|
||||
</div>
|
||||
<div style="text-align:center; margin-bottom: 20px;">
|
||||
<a href="/reset_scores/{{ playlist_id }}?mode={{ game_mode }}&local_multiplayer=1" class="btn" style="background:linear-gradient(135deg, #FFA500 0%, #FF8C00 100%); box-shadow: 0 4px 15px rgba(255, 165, 0, 0.3); margin-top:10px;" onclick="return confirm('Alle Punkte zurücksetzen?')">🔄 Punkte zurücksetzen</a>
|
||||
<a href="/reset_quiz/{{ playlist_id }}?local_multiplayer=1" class="btn btn-danger" style="margin-top:10px;">{{ translations['end_quiz'] }}</a>
|
||||
</div>
|
||||
<h2 id="question-text" style="color:#fff;">{{ translations['question_artist'] }}</h2>
|
||||
|
||||
@@ -40,62 +40,53 @@
|
||||
text-align: center;
|
||||
}
|
||||
.team-count-label {
|
||||
font-size: 1.3em;
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 15px;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.team-count-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.count-btn {
|
||||
padding: 15px 30px;
|
||||
border-radius: 15px;
|
||||
border: 2px solid rgba(29, 185, 84, 0.3);
|
||||
background: rgba(15, 20, 25, 0.8);
|
||||
color: #e0e0e0;
|
||||
font-size: 1.2em;
|
||||
font-weight: 600;
|
||||
}
|
||||
.team-count-display {
|
||||
font-size: 3em;
|
||||
font-weight: bold;
|
||||
color: #1DB954;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.team-count-slider {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
border-radius: 5px;
|
||||
background: rgba(29, 185, 84, 0.2);
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
.team-count-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
background: #1DB954;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 10px rgba(29, 185, 84, 0.5);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.team-count-slider::-moz-range-thumb {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
background: #1DB954;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 10px rgba(29, 185, 84, 0.5);
|
||||
border: none;
|
||||
.count-btn:hover {
|
||||
border-color: #1DB954;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.teams-list {
|
||||
margin: 30px 0;
|
||||
.count-btn.active {
|
||||
background: linear-gradient(135deg, #1DB954 0%, #1ed760 100%);
|
||||
border-color: #1DB954;
|
||||
color: white;
|
||||
box-shadow: 0 4px 15px rgba(29, 185, 84, 0.5);
|
||||
}
|
||||
.teams-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.team-input-group {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
.team-number {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
.team-label {
|
||||
display: block;
|
||||
font-size: 1.1em;
|
||||
margin-bottom: 8px;
|
||||
color: #1DB954;
|
||||
min-width: 100px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.team-name-input {
|
||||
flex: 1;
|
||||
padding: 12px 20px;
|
||||
.team-input {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
border-radius: 12px;
|
||||
border: 2px solid rgba(29, 185, 84, 0.3);
|
||||
background: rgba(15, 20, 25, 0.8);
|
||||
@@ -103,11 +94,15 @@
|
||||
font-size: 1.1em;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.team-name-input:focus {
|
||||
.team-input:focus {
|
||||
outline: none;
|
||||
border-color: #1DB954;
|
||||
box-shadow: 0 0 10px rgba(29, 185, 84, 0.3);
|
||||
}
|
||||
.team-input.disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 15px 30px;
|
||||
@@ -140,54 +135,235 @@
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.buzzer-mode-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background: rgba(15, 20, 25, 0.5);
|
||||
border-radius: 15px;
|
||||
border: 2px solid rgba(29, 185, 84, 0.2);
|
||||
}
|
||||
.mode-label {
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 15px;
|
||||
color: #e0e0e0;
|
||||
text-align: center;
|
||||
}
|
||||
.mode-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.mode-btn {
|
||||
padding: 12px 25px;
|
||||
border-radius: 12px;
|
||||
border: 2px solid rgba(29, 185, 84, 0.3);
|
||||
background: rgba(15, 20, 25, 0.8);
|
||||
color: #e0e0e0;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.mode-btn:hover {
|
||||
border-color: #1DB954;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.mode-btn.active {
|
||||
background: linear-gradient(135deg, #1DB954 0%, #1ed760 100%);
|
||||
border-color: #1DB954;
|
||||
color: white;
|
||||
box-shadow: 0 4px 15px rgba(29, 185, 84, 0.5);
|
||||
}
|
||||
.key-mapping-section {
|
||||
display: none;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.key-mapping-section.active {
|
||||
display: block;
|
||||
}
|
||||
.key-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.key-label {
|
||||
flex: 1;
|
||||
font-size: 1em;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.key-input {
|
||||
flex: 1;
|
||||
padding: 10px 15px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid rgba(29, 185, 84, 0.3);
|
||||
background: rgba(15, 20, 25, 0.9);
|
||||
color: #1DB954;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.key-input:focus {
|
||||
outline: none;
|
||||
border-color: #1DB954;
|
||||
box-shadow: 0 0 10px rgba(29, 185, 84, 0.5);
|
||||
}
|
||||
.key-input.disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.info-text {
|
||||
font-size: 0.9em;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
let teamCount = 4;
|
||||
let teamCount = 2;
|
||||
let buzzerMode = 'central'; // 'central' oder 'personal'
|
||||
let keyMappings = {1: 'Q', 2: 'W', 3: 'E', 4: 'R'};
|
||||
|
||||
function updateTeamCount() {
|
||||
teamCount = parseInt(document.getElementById('teamCountSlider').value);
|
||||
document.getElementById('teamCountDisplay').textContent = teamCount;
|
||||
updateTeamInputs();
|
||||
function setBuzzerMode(mode) {
|
||||
buzzerMode = mode;
|
||||
|
||||
// Update aktive Button
|
||||
document.querySelectorAll('.mode-btn').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
document.getElementById(`mode-${mode}`).classList.add('active');
|
||||
|
||||
// Zeige/Verstecke Tastenauswahl
|
||||
const keySection = document.getElementById('keyMappingSection');
|
||||
if (mode === 'personal') {
|
||||
keySection.classList.add('active');
|
||||
} else {
|
||||
keySection.classList.remove('active');
|
||||
}
|
||||
|
||||
localStorage.setItem('buzzer_mode', mode);
|
||||
}
|
||||
|
||||
function updateTeamInputs() {
|
||||
const container = document.getElementById('teamInputs');
|
||||
container.innerHTML = '';
|
||||
function setKeyForPlayer(playerNum) {
|
||||
const input = document.getElementById(`key${playerNum}`);
|
||||
if (input.disabled) return;
|
||||
|
||||
for (let i = 1; i <= teamCount; i++) {
|
||||
const savedName = localStorage.getItem(`team_${i}_name`) || `Team ${i}`;
|
||||
input.value = 'Drücke eine Taste...';
|
||||
input.focus();
|
||||
|
||||
const group = document.createElement('div');
|
||||
group.className = 'team-input-group';
|
||||
group.innerHTML = `
|
||||
<div class="team-number">Team ${i}:</div>
|
||||
<input type="text"
|
||||
class="team-name-input"
|
||||
id="team${i}Name"
|
||||
value="${savedName}"
|
||||
placeholder="Team ${i} Name"
|
||||
maxlength="20">
|
||||
`;
|
||||
container.appendChild(group);
|
||||
const handleKeyPress = (e) => {
|
||||
e.preventDefault();
|
||||
const key = e.key.toUpperCase();
|
||||
|
||||
// Entferne Event-Listener sofort
|
||||
document.removeEventListener('keydown', handleKeyPress);
|
||||
|
||||
// Prüfe ob Taste bereits vergeben
|
||||
let isUsed = false;
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
if (i !== playerNum && keyMappings[i] === key) {
|
||||
isUsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isUsed) {
|
||||
alert(`Die Taste "${key}" ist bereits vergeben!`);
|
||||
input.value = keyMappings[playerNum];
|
||||
input.blur();
|
||||
return;
|
||||
}
|
||||
|
||||
keyMappings[playerNum] = key;
|
||||
input.value = key;
|
||||
input.blur();
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyPress);
|
||||
}
|
||||
|
||||
function setTeamCount(count) {
|
||||
teamCount = count;
|
||||
|
||||
// Update aktive Button
|
||||
document.querySelectorAll('.count-btn').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
document.getElementById(`count${count}`).classList.add('active');
|
||||
|
||||
// Enable/Disable Inputs
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const input = document.getElementById(`team${i}`);
|
||||
const keyInput = document.getElementById(`key${i}`);
|
||||
|
||||
if (i <= count) {
|
||||
input.disabled = false;
|
||||
input.classList.remove('disabled');
|
||||
if (keyInput) {
|
||||
keyInput.disabled = false;
|
||||
keyInput.classList.remove('disabled');
|
||||
}
|
||||
} else {
|
||||
input.disabled = true;
|
||||
input.classList.add('disabled');
|
||||
if (keyInput) {
|
||||
keyInput.disabled = true;
|
||||
keyInput.classList.add('disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Speichere in localStorage
|
||||
localStorage.setItem('team_count', count);
|
||||
}
|
||||
|
||||
function saveTeams() {
|
||||
// Speichere Anzahl
|
||||
localStorage.setItem('team_count', teamCount);
|
||||
|
||||
// Speichere Namen
|
||||
const teams = [];
|
||||
for (let i = 1; i <= teamCount; i++) {
|
||||
const name = document.getElementById(`team${i}Name`).value || `Team ${i}`;
|
||||
localStorage.setItem(`team_${i}_name`, name);
|
||||
const name = document.getElementById(`team${i}`).value || `Spieler ${i}`;
|
||||
teams.push(name);
|
||||
}
|
||||
|
||||
// Speichere in localStorage
|
||||
localStorage.setItem('team_names', JSON.stringify(teams));
|
||||
localStorage.setItem('team_count', teamCount);
|
||||
localStorage.setItem('buzzer_mode', buzzerMode);
|
||||
localStorage.setItem('key_mappings', JSON.stringify(keyMappings));
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
// Lade gespeicherte Anzahl
|
||||
const savedCount = localStorage.getItem('team_count') || '4';
|
||||
document.getElementById('teamCountSlider').value = savedCount;
|
||||
updateTeamCount();
|
||||
// Lade gespeicherte Werte
|
||||
const savedCount = parseInt(localStorage.getItem('team_count')) || 2;
|
||||
const savedNames = JSON.parse(localStorage.getItem('team_names') || '[]');
|
||||
const savedMode = localStorage.getItem('buzzer_mode') || 'central';
|
||||
const savedKeys = JSON.parse(localStorage.getItem('key_mappings') || '{"1":"Q","2":"W","3":"E","4":"R"}');
|
||||
|
||||
// Setze Team-Anzahl
|
||||
setTeamCount(savedCount);
|
||||
|
||||
// Setze Team-Namen
|
||||
savedNames.forEach((name, index) => {
|
||||
if (index < 4) {
|
||||
document.getElementById(`team${index + 1}`).value = name;
|
||||
}
|
||||
});
|
||||
|
||||
// Setze Buzzer-Modus
|
||||
setBuzzerMode(savedMode);
|
||||
|
||||
// Setze Tasten
|
||||
keyMappings = savedKeys;
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const keyInput = document.getElementById(`key${i}`);
|
||||
if (keyInput) {
|
||||
keyInput.value = keyMappings[i] || 'Q';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
@@ -196,29 +372,79 @@
|
||||
<h2>👥 Team Setup</h2>
|
||||
|
||||
<div class="team-count-section">
|
||||
<div class="team-count-label">Anzahl der Teams</div>
|
||||
<div class="team-count-display" id="teamCountDisplay">4</div>
|
||||
<input type="range"
|
||||
class="team-count-slider"
|
||||
id="teamCountSlider"
|
||||
min="2"
|
||||
max="6"
|
||||
value="4"
|
||||
oninput="updateTeamCount()">
|
||||
<div class="team-count-label">Wie viele Teams spielen mit?</div>
|
||||
<div class="team-count-buttons">
|
||||
<button class="count-btn active" id="count2" onclick="setTeamCount(2)">2 Teams</button>
|
||||
<button class="count-btn" id="count3" onclick="setTeamCount(3)">3 Teams</button>
|
||||
<button class="count-btn" id="count4" onclick="setTeamCount(4)">4 Teams</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="teams-list">
|
||||
<div id="teamInputs"></div>
|
||||
<div class="buzzer-mode-section">
|
||||
<div class="mode-label">🎮 Buzzer-Modus</div>
|
||||
<div class="mode-buttons">
|
||||
<button class="mode-btn active" id="mode-central" onclick="setBuzzerMode('central')">
|
||||
Zentraler Modus<br>
|
||||
<span style="font-size:0.85em; font-weight:normal;">(Eine Taste für alle)</span>
|
||||
</button>
|
||||
<button class="mode-btn" id="mode-personal" onclick="setBuzzerMode('personal')">
|
||||
Persönlicher Modus<br>
|
||||
<span style="font-size:0.85em; font-weight:normal;">(Eigene Taste pro Spieler)</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="key-mapping-section" id="keyMappingSection">
|
||||
<div class="info-text">Klicke auf ein Feld und drücke die gewünschte Taste</div>
|
||||
|
||||
<div class="key-input-group">
|
||||
<div class="key-label">Team 1 Buzzer:</div>
|
||||
<input type="text" class="key-input" id="key1" value="Q" readonly onclick="setKeyForPlayer(1)">
|
||||
</div>
|
||||
|
||||
<div class="key-input-group">
|
||||
<div class="key-label">Team 2 Buzzer:</div>
|
||||
<input type="text" class="key-input" id="key2" value="W" readonly onclick="setKeyForPlayer(2)">
|
||||
</div>
|
||||
|
||||
<div class="key-input-group">
|
||||
<div class="key-label">Team 3 Buzzer:</div>
|
||||
<input type="text" class="key-input disabled" id="key3" value="E" readonly onclick="setKeyForPlayer(3)" disabled>
|
||||
</div>
|
||||
|
||||
<div class="key-input-group">
|
||||
<div class="key-label">Team 4 Buzzer:</div>
|
||||
<input type="text" class="key-input disabled" id="key4" value="R" readonly onclick="setKeyForPlayer(4)" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="teams-section">
|
||||
<div class="team-input-group">
|
||||
<label class="team-label" for="team1">Team 1</label>
|
||||
<input type="text" class="team-input" id="team1" placeholder="Team 1 Name" value="Spieler 1">
|
||||
</div>
|
||||
|
||||
<div class="team-input-group">
|
||||
<label class="team-label" for="team2">Team 2</label>
|
||||
<input type="text" class="team-input" id="team2" placeholder="Team 2 Name" value="Spieler 2">
|
||||
</div>
|
||||
|
||||
<div class="team-input-group">
|
||||
<label class="team-label" for="team3">Team 3</label>
|
||||
<input type="text" class="team-input disabled" id="team3" placeholder="Team 3 Name" value="Spieler 3" disabled>
|
||||
</div>
|
||||
|
||||
<div class="team-input-group">
|
||||
<label class="team-label" for="team4">Team 4</label>
|
||||
<input type="text" class="team-input disabled" id="team4" placeholder="Team 4 Name" value="Spieler 4" disabled>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<a href="{{ url_for('buzzer_settings', playlist_id=playlist_id) }}?mode={{ game_mode }}&local_multiplayer=1"
|
||||
class="btn btn-primary"
|
||||
onclick="saveTeams()">
|
||||
✅ Weiter zu Einstellungen
|
||||
<a href="{{ url_for('buzzer_settings', playlist_id=playlist_id, mode=game_mode, local_multiplayer=1) }}" class="btn btn-primary" onclick="saveTeams()">
|
||||
✅ Weiter
|
||||
</a>
|
||||
<a href="{{ url_for('playerselect', playlist_id=playlist_id) }}?mode={{ game_mode }}&buzzer=1"
|
||||
class="btn btn-secondary">
|
||||
<a href="{{ url_for('playerselect', playlist_id=playlist_id, mode=game_mode, buzzer=1) }}" class="btn btn-secondary">
|
||||
⬅️ Zurück
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user