feat: implement answer submission and scoring system for guessing game

This commit is contained in:
2025-09-04 15:11:03 +02:00
parent bbce3cbadf
commit 158d144721
7 changed files with 211 additions and 498 deletions

View File

@@ -125,6 +125,20 @@
<input id="volumeSlider" type="range" min="0" max="1" step="0.01" class="w-40 accent-indigo-600" />
</label>
</div>
<!-- Answer form: everyone can try to guess title & artist for a coin -->
<form id="answerForm" class="mt-4 w-full flex flex-col md:flex-row gap-2 md:items-end">
<label class="flex-1 text-sm">
<span class="text-slate-700 dark:text-slate-300">Titel</span>
<input id="guessTitle" name="title" placeholder="Songtitel" autocomplete="off" class="mt-1 w-full h-10 rounded-lg border border-slate-300 dark:border-slate-700 bg-white/80 dark:bg-slate-800 px-3" />
</label>
<label class="flex-1 text-sm">
<span class="text-slate-700 dark:text-slate-300">Künstler</span>
<input id="guessArtist" name="artist" placeholder="Künstler" autocomplete="off" class="mt-1 w-full h-10 rounded-lg border border-slate-300 dark:border-slate-700 bg-white/80 dark:bg-slate-800 px-3" />
</label>
<button id="submitAnswer" type="submit" class="h-10 px-4 rounded-lg bg-emerald-600 hover:bg-emerald-700 text-white font-medium">Abschicken</button>
</form>
<div id="answerResult" class="mt-1 text-sm"></div>
</div>
</div>
<div class="mt-3 flex flex-wrap items-center gap-3">
@@ -147,7 +161,6 @@
<div class="flex items-center gap-3 text-slate-700 dark:text-slate-300">
Tokens: <span id="tokens" class="font-semibold">0</span>
<button id="earnToken" class="h-9 px-3 rounded-lg border border-slate-300 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 text-sm">+1 (Titel & Künstler richtig)</button>
</div>
</div>
</div>

View File

@@ -38,6 +38,11 @@ export const $leaveRoom = el('leaveRoom');
export const $earnToken = el('earnToken');
export const $dashboardList = el('dashboardList');
export const $toast = el('toast');
// Answer form elements
export const $answerForm = el('answerForm');
export const $guessTitle = el('guessTitle');
export const $guessArtist = el('guessArtist');
export const $answerResult = el('answerResult');
export function showLobby() { $lobby.classList.remove('hidden'); $room.classList.add('hidden'); }
export function showRoom() { $lobby.classList.add('hidden'); $room.classList.remove('hidden'); }

View File

@@ -1,4 +1,4 @@
import { $audio, $copyRoomCode, $leaveRoom, $nameDisplay, $nameLobby, $npArtist, $npTitle, $npYear, $pauseBtn, $placeBtn, $readyChk, $roomId, $roomCode, $slotSelect, $startGame, $volumeSlider, $playBtn, $nextBtn, $createRoom, $joinRoom, $lobby, $room, $setNameLobby } from './dom.js';
import { $audio, $copyRoomCode, $leaveRoom, $nameDisplay, $nameLobby, $npArtist, $npTitle, $npYear, $pauseBtn, $placeBtn, $readyChk, $roomId, $roomCode, $slotSelect, $startGame, $volumeSlider, $playBtn, $nextBtn, $createRoom, $joinRoom, $lobby, $room, $setNameLobby, $guessTitle, $guessArtist, $answerResult } from './dom.js';
import { state } from './state.js';
import { connectWS, sendMsg } from './ws.js';
import { renderRoom } from './render.js';
@@ -44,6 +44,10 @@ function handlePlayTrack(msg) {
$npTitle.textContent = '???';
$npArtist.textContent = '';
$npYear.textContent = '';
// reset answer UI
if ($guessTitle) $guessTitle.value = '';
if ($guessArtist) $guessArtist.value = '';
if ($answerResult) { $answerResult.textContent=''; $answerResult.className='mt-1 text-sm'; }
try {
$audio.preload = 'auto';
} catch {}
@@ -151,6 +155,25 @@ function onMessage(ev) {
case 'game_ended':
handleGameEnded(msg);
break;
case 'answer_result': {
if ($answerResult) {
if (!msg.ok) {
$answerResult.textContent = '⛔ Eingabe ungültig oder gerade nicht möglich';
$answerResult.className = 'mt-1 text-sm text-rose-600';
} else {
const okBoth = !!(msg.correctTitle && msg.correctArtist);
const parts = [];
parts.push(msg.correctTitle ? 'Titel ✓' : 'Titel ✗');
parts.push(msg.correctArtist ? 'Künstler ✓' : 'Künstler ✗');
let coin = '';
if (msg.awarded) coin = ' +1 Token';
else if (msg.alreadyAwarded) coin = ' (bereits erhalten)';
$answerResult.textContent = `${parts.join(' · ')}${coin}`;
$answerResult.className = okBoth ? 'mt-1 text-sm text-emerald-600' : 'mt-1 text-sm text-amber-600';
}
}
break;
}
default:
break;
}
@@ -241,6 +264,20 @@ function wireUi() {
});
$roomId.style.cursor = 'pointer';
}
// Answer submit
const form = document.getElementById('answerForm');
if (form) {
form.addEventListener('submit', (e) => {
e.preventDefault();
const title = ($guessTitle?.value || '').trim();
const artist = ($guessArtist?.value || '').trim();
if (!title || !artist) {
if ($answerResult) { $answerResult.textContent = 'Bitte Titel und Künstler eingeben'; $answerResult.className = 'mt-1 text-sm text-amber-600'; }
return;
}
sendMsg({ type: 'submit_answer', guess: { title, artist } });
});
}
}
// boot

View File

@@ -1,6 +1,6 @@
import { state } from './state.js';
import { badgeColorForYear } from '../utils/colors.js';
import { $dashboardList, $guesser, $lobby, $nameDisplay, $nextArea, $np, $placeArea, $readyChk, $revealBanner, $room, $roomId, $slotSelect, $startGame, $status, $timeline, $tokens } from './dom.js';
import { $answerForm, $answerResult, $dashboardList, $guesser, $lobby, $nameDisplay, $nextArea, $np, $placeArea, $readyChk, $revealBanner, $room, $roomId, $slotSelect, $startGame, $status, $timeline, $tokens } from './dom.js';
export function renderRoom(room) {
state.room = room; if (!room) { $lobby.classList.remove('hidden'); $room.classList.add('hidden'); return; }
@@ -87,6 +87,10 @@ export function renderRoom(room) {
if ($revealBanner) { const inReveal = room.state.phase === 'reveal'; if (!inReveal) { $revealBanner.className = 'hidden'; $revealBanner.textContent=''; } }
const canNext = room.state.status==='playing' && room.state.phase==='reveal' && (isHost || room.state.currentGuesser===state.playerId);
if ($nextArea) $nextArea.classList.toggle('hidden', !canNext);
// Answer form visible during guess phase while a track is active
const showAnswer = room.state.status==='playing' && room.state.phase==='guess' && !!room.state.currentTrack;
if ($answerForm) $answerForm.classList.toggle('hidden', !showAnswer);
if ($answerResult && !showAnswer) { $answerResult.textContent=''; $answerResult.className='mt-1 text-sm'; }
}
export function shortName(id) {