nearly finalized, fix the questions
This commit is contained in:
69
display.html
69
display.html
@@ -35,12 +35,15 @@
|
||||
<div class="light player-4" id="light4"></div>
|
||||
</div>
|
||||
<div class="buzzed-player" id="buzzedPlayer"></div>
|
||||
<div class="timer-display" id="timerDisplay" style="display: none;">
|
||||
Time: 5s
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="question-section">
|
||||
<div class="question-header">
|
||||
<span class="question-category" id="questionCategory">CATEGORY</span>
|
||||
<span class="question-number">Question <span id="questionNum">1</span>/25</span>
|
||||
<span class="question-number">Question <span id="questionNum">1</span></span>
|
||||
</div>
|
||||
<div class="question-text" id="questionText">
|
||||
Click "New Question" to start
|
||||
@@ -64,22 +67,54 @@
|
||||
<button id="resetGame" class="control-btn warning">Reset Game</button>
|
||||
</div>
|
||||
|
||||
<div class="category-filter">
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="PHYSICS" checked> Physics
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="CHEMISTRY" checked> Chemistry
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="BIOLOGY" checked> Biology
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="MATH" checked> Math
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="EARTH SCIENCE" checked> Earth Science
|
||||
</label>
|
||||
<div class="settings-section">
|
||||
<div class="setting-group">
|
||||
<h3>📚 Source Selection</h3>
|
||||
<select id="sourceSelect" class="source-select">
|
||||
<option value="">All Sources (Random)</option>
|
||||
<option value="Official">Official Sample Sets</option>
|
||||
<option value="CSUB">CSUB Regionals</option>
|
||||
<option value="05Nats">2005 Nationals</option>
|
||||
<option value="98Nats">1998 Nationals</option>
|
||||
</select>
|
||||
<input type="number" id="roundNumber" placeholder="Round # (optional)" min="1" max="25" class="round-input">
|
||||
</div>
|
||||
|
||||
<div class="setting-group">
|
||||
<h3>🔬 Category Filter</h3>
|
||||
<div class="category-filter">
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="PHYSICS" checked> Physics
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="CHEMISTRY" checked> Chemistry
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="BIOLOGY" checked> Biology
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="MATH" checked> Math
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="EARTH SCIENCE" checked> Earth Science
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="EARTH AND SPACE" checked> Earth & Space
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="ASTRONOMY" checked> Astronomy
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="ENERGY" checked> Energy
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="GENERAL SCIENCE" checked> General Science
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="category-checkbox" value="COMPUTER SCIENCE" checked> Computer Science
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
342
display.js
342
display.js
@@ -7,9 +7,9 @@ const questionText = document.getElementById('questionText');
|
||||
const questionCategory = document.getElementById('questionCategory');
|
||||
const questionNum = document.getElementById('questionNum');
|
||||
const correctAnswer = document.getElementById('correctAnswer');
|
||||
const playerAnswer = document.getElementById('playerAnswer');
|
||||
const answerSection = document.getElementById('answerSection');
|
||||
const buzzedPlayer = document.getElementById('buzzedPlayer');
|
||||
const timerDisplay = document.getElementById('timerDisplay');
|
||||
|
||||
const newQuestionBtn = document.getElementById('newQuestion');
|
||||
const activateBuzzerBtn = document.getElementById('activateBuzzer');
|
||||
@@ -24,6 +24,64 @@ let currentQuestionNumber = 1;
|
||||
let currentBuzzedPlayer = null;
|
||||
let speechSynthesis = window.speechSynthesis;
|
||||
let currentUtterance = null;
|
||||
let voicesLoaded = false;
|
||||
let isShowingBonus = false;
|
||||
let timerInterval = null;
|
||||
let timeRemaining = 0;
|
||||
|
||||
// Track used questions to avoid repeats
|
||||
let usedQuestionIds = new Set();
|
||||
let allQuestions = [];
|
||||
let questionsLoaded = false;
|
||||
|
||||
// Load voices
|
||||
function loadVoices() {
|
||||
return new Promise((resolve) => {
|
||||
let voices = speechSynthesis.getVoices();
|
||||
if (voices.length > 0) {
|
||||
voicesLoaded = true;
|
||||
resolve(voices);
|
||||
} else {
|
||||
speechSynthesis.onvoiceschanged = () => {
|
||||
voices = speechSynthesis.getVoices();
|
||||
voicesLoaded = true;
|
||||
resolve(voices);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadVoices().then(() => {
|
||||
console.log('Voices loaded:', speechSynthesis.getVoices().length);
|
||||
});
|
||||
|
||||
// Load all questions once at startup
|
||||
async function loadAllQuestions() {
|
||||
if (questionsLoaded) return;
|
||||
|
||||
try {
|
||||
console.log('Loading all questions from database...');
|
||||
const response = await fetch(CORS_PROXY + encodeURIComponent('https://scibowldb.com/api/questions'), {
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load questions');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
allQuestions = data.questions || data;
|
||||
questionsLoaded = true;
|
||||
console.log(`Loaded ${allQuestions.length} questions!`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading questions:', error);
|
||||
alert('Could not load question database. Will use random API instead (may repeat questions).');
|
||||
}
|
||||
}
|
||||
|
||||
// Start loading questions immediately
|
||||
loadAllQuestions();
|
||||
|
||||
// Initialize game state
|
||||
database.ref('gameState').set({
|
||||
@@ -55,63 +113,148 @@ database.ref('gameState').on('value', (snapshot) => {
|
||||
const playerNum = state.buzzer.playerId.replace('player', '');
|
||||
currentBuzzedPlayer = state.buzzer.playerId;
|
||||
|
||||
// Light up the buzzer
|
||||
document.querySelectorAll('.light').forEach(l => l.classList.remove('active'));
|
||||
document.getElementById(`light${playerNum}`).classList.add('active');
|
||||
|
||||
buzzedPlayer.textContent = `Player ${playerNum} buzzed in!`;
|
||||
|
||||
// Stop reading when someone buzzes
|
||||
if (currentUtterance) {
|
||||
// Stop reading and timer when someone buzzes
|
||||
if (currentUtterance && speechSynthesis.speaking) {
|
||||
speechSynthesis.cancel();
|
||||
readQuestionBtn.textContent = '🔊 Read Question';
|
||||
readQuestionBtn.disabled = false;
|
||||
}
|
||||
stopTimer();
|
||||
|
||||
// Show grading buttons
|
||||
markCorrectBtn.style.display = 'inline-block';
|
||||
markIncorrectBtn.style.display = 'inline-block';
|
||||
showAnswerBtn.style.display = 'inline-block';
|
||||
}
|
||||
|
||||
// Update player answer
|
||||
if (state.playerAnswer) {
|
||||
playerAnswer.textContent = state.playerAnswer.answer;
|
||||
answerSection.style.display = 'block';
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch new question
|
||||
// Timer functions
|
||||
function startTimer(seconds) {
|
||||
stopTimer();
|
||||
timeRemaining = seconds;
|
||||
timerDisplay.textContent = `Time: ${seconds}s`;
|
||||
timerDisplay.style.display = 'block';
|
||||
|
||||
timerInterval = setInterval(() => {
|
||||
timeRemaining--;
|
||||
timerDisplay.textContent = `Time: ${timeRemaining}s`;
|
||||
|
||||
if (timeRemaining <= 0) {
|
||||
stopTimer();
|
||||
timerDisplay.textContent = 'Time\'s up!';
|
||||
setTimeout(() => {
|
||||
timerDisplay.style.display = 'none';
|
||||
}, 2000);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function stopTimer() {
|
||||
if (timerInterval) {
|
||||
clearInterval(timerInterval);
|
||||
timerInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Get filtered questions based on user selection
|
||||
function getFilteredQuestions() {
|
||||
const categories = Array.from(document.querySelectorAll('.category-checkbox:checked'))
|
||||
.map(cb => cb.value);
|
||||
|
||||
const source = document.getElementById('sourceSelect').value;
|
||||
const roundNum = document.getElementById('roundNumber').value;
|
||||
|
||||
let filtered = allQuestions.filter(q => {
|
||||
// Filter by category
|
||||
if (categories.length > 0 && !categories.includes(q.category)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter by source
|
||||
if (source && !q.source.startsWith(source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter by round number
|
||||
if (roundNum && !q.source.includes(`round${roundNum}`)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exclude already used questions
|
||||
if (usedQuestionIds.has(q.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
// Fetch new question with filtering
|
||||
newQuestionBtn.addEventListener('click', async () => {
|
||||
const categories = Array.from(document.querySelectorAll('.category-checkbox:checked'))
|
||||
.map(cb => cb.value);
|
||||
|
||||
if (categories.length === 0) {
|
||||
alert('Please select at least one category!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for questions to load if not ready
|
||||
if (!questionsLoaded) {
|
||||
newQuestionBtn.textContent = 'Loading questions...';
|
||||
newQuestionBtn.disabled = true;
|
||||
await loadAllQuestions();
|
||||
newQuestionBtn.textContent = 'New Question';
|
||||
newQuestionBtn.disabled = false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Just fetch a random question without filtering
|
||||
const response = await fetch(CORS_PROXY + encodeURIComponent(API_URL), {
|
||||
method: 'GET' // Changed to GET - simpler
|
||||
});
|
||||
const availableQuestions = getFilteredQuestions();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('API request failed');
|
||||
if (availableQuestions.length === 0) {
|
||||
const shouldReset = confirm('No more unique questions available with these filters! Reset used questions?');
|
||||
if (shouldReset) {
|
||||
usedQuestionIds.clear();
|
||||
newQuestionBtn.click();
|
||||
return;
|
||||
} else {
|
||||
alert('Try changing your filters or reset the game.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
// Pick a random question from available ones
|
||||
const randomIndex = Math.floor(Math.random() * availableQuestions.length);
|
||||
currentQuestion = availableQuestions[randomIndex];
|
||||
|
||||
// The API returns the question directly, not wrapped
|
||||
currentQuestion = data;
|
||||
// Mark this question as used
|
||||
usedQuestionIds.add(currentQuestion.id);
|
||||
|
||||
console.log('Fetched question:', currentQuestion);
|
||||
isShowingBonus = false;
|
||||
|
||||
console.log('Selected question:', currentQuestion);
|
||||
console.log('Questions remaining:', availableQuestions.length - 1);
|
||||
|
||||
questionText.textContent = currentQuestion.tossup_question;
|
||||
questionCategory.textContent = currentQuestion.category;
|
||||
questionCategory.textContent = `${currentQuestion.category} - TOSSUP`;
|
||||
correctAnswer.textContent = currentQuestion.tossup_answer;
|
||||
questionNum.textContent = currentQuestionNumber;
|
||||
|
||||
// Update Firebase
|
||||
// Show source info
|
||||
if (currentQuestion.source) {
|
||||
questionCategory.textContent = `${currentQuestion.category} - TOSSUP (${currentQuestion.source})`;
|
||||
}
|
||||
|
||||
await database.ref('gameState').update({
|
||||
currentQuestion: currentQuestion,
|
||||
buzzerActive: false,
|
||||
buzzer: null,
|
||||
playerAnswer: null,
|
||||
questionNumber: currentQuestionNumber,
|
||||
isReading: false
|
||||
});
|
||||
@@ -119,6 +262,7 @@ newQuestionBtn.addEventListener('click', async () => {
|
||||
// Reset UI
|
||||
answerSection.style.display = 'none';
|
||||
buzzedPlayer.textContent = '';
|
||||
timerDisplay.style.display = 'none';
|
||||
document.querySelectorAll('.light').forEach(l => l.classList.remove('active'));
|
||||
markCorrectBtn.style.display = 'none';
|
||||
markIncorrectBtn.style.display = 'none';
|
||||
@@ -132,50 +276,78 @@ newQuestionBtn.addEventListener('click', async () => {
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching question:', error);
|
||||
alert('Error loading question. The API might be down. Check console for details.');
|
||||
alert('Error loading question. Try refreshing the page.');
|
||||
}
|
||||
});
|
||||
|
||||
// Read question aloud
|
||||
readQuestionBtn.addEventListener('click', () => {
|
||||
readQuestionBtn.addEventListener('click', async () => {
|
||||
if (!currentQuestion) {
|
||||
alert('Load a question first!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop any current speech
|
||||
speechSynthesis.cancel();
|
||||
if (!voicesLoaded) {
|
||||
await loadVoices();
|
||||
}
|
||||
|
||||
// Create utterance
|
||||
currentUtterance = new SpeechSynthesisUtterance(currentQuestion.tossup_question);
|
||||
currentUtterance.rate = 0.9; // Slightly slower for clarity
|
||||
currentUtterance.pitch = 1;
|
||||
currentUtterance.volume = 1;
|
||||
if (speechSynthesis.speaking) {
|
||||
speechSynthesis.cancel();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
const textToRead = isShowingBonus ? currentQuestion.bonus_question : currentQuestion.tossup_question;
|
||||
currentUtterance = new SpeechSynthesisUtterance(textToRead);
|
||||
|
||||
// Select best voice
|
||||
const voices = speechSynthesis.getVoices();
|
||||
const preferredVoice = voices.find(v => v.name.includes('Google') && v.lang.startsWith('en')) ||
|
||||
voices.find(v => v.lang === 'en-US' && v.name.includes('Natural')) ||
|
||||
voices.find(v => v.lang === 'en-US') ||
|
||||
voices[0];
|
||||
|
||||
if (preferredVoice) {
|
||||
currentUtterance.voice = preferredVoice;
|
||||
}
|
||||
|
||||
currentUtterance.rate = 0.9;
|
||||
currentUtterance.pitch = 1.0;
|
||||
currentUtterance.volume = 1.0;
|
||||
|
||||
console.log('Reading with voice:', preferredVoice?.name);
|
||||
|
||||
// Update Firebase that we're reading
|
||||
database.ref('gameState/isReading').set(true);
|
||||
|
||||
currentUtterance.onstart = () => {
|
||||
readQuestionBtn.textContent = '🔊 Reading...';
|
||||
readQuestionBtn.disabled = true;
|
||||
};
|
||||
|
||||
currentUtterance.onend = () => {
|
||||
database.ref('gameState/isReading').set(false);
|
||||
console.log('Finished reading question');
|
||||
readQuestionBtn.textContent = '🔊 Read Question';
|
||||
readQuestionBtn.disabled = false;
|
||||
|
||||
// Start timer after reading
|
||||
const timerDuration = isShowingBonus ? 20 : 5;
|
||||
startTimer(timerDuration);
|
||||
};
|
||||
|
||||
currentUtterance.onerror = (event) => {
|
||||
console.error('Speech error:', event);
|
||||
database.ref('gameState/isReading').set(false);
|
||||
readQuestionBtn.textContent = '🔊 Read Question';
|
||||
readQuestionBtn.disabled = false;
|
||||
};
|
||||
|
||||
speechSynthesis.speak(currentUtterance);
|
||||
readQuestionBtn.textContent = '🔊 Reading...';
|
||||
readQuestionBtn.disabled = true;
|
||||
|
||||
setTimeout(() => {
|
||||
readQuestionBtn.textContent = '🔊 Read Question';
|
||||
readQuestionBtn.disabled = false;
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
// Activate buzzers
|
||||
activateBuzzerBtn.addEventListener('click', () => {
|
||||
database.ref('gameState').update({
|
||||
buzzerActive: true,
|
||||
buzzer: null,
|
||||
playerAnswer: null
|
||||
buzzer: null
|
||||
});
|
||||
activateBuzzerBtn.disabled = true;
|
||||
answerSection.style.display = 'none';
|
||||
@@ -185,32 +357,66 @@ activateBuzzerBtn.addEventListener('click', () => {
|
||||
markCorrectBtn.addEventListener('click', () => {
|
||||
if (!currentBuzzedPlayer) return;
|
||||
|
||||
const points = isShowingBonus ? 10 : 4;
|
||||
|
||||
database.ref(`gameState/scores/${currentBuzzedPlayer}`).transaction((score) => {
|
||||
return (score || 0) + 4;
|
||||
return (score || 0) + points;
|
||||
});
|
||||
|
||||
resetForNextQuestion();
|
||||
// If tossup was correct, show bonus
|
||||
if (!isShowingBonus && currentQuestion.bonus_question) {
|
||||
showBonus();
|
||||
} else {
|
||||
resetForNextQuestion();
|
||||
}
|
||||
});
|
||||
|
||||
// Show bonus question
|
||||
function showBonus() {
|
||||
isShowingBonus = true;
|
||||
questionText.textContent = currentQuestion.bonus_question;
|
||||
questionCategory.textContent = currentQuestion.category + ' - BONUS';
|
||||
correctAnswer.textContent = currentQuestion.bonus_answer;
|
||||
|
||||
// Reset UI for bonus
|
||||
buzzedPlayer.textContent = '';
|
||||
document.querySelectorAll('.light').forEach(l => l.classList.remove('active'));
|
||||
markCorrectBtn.style.display = 'none';
|
||||
markIncorrectBtn.style.display = 'none';
|
||||
showAnswerBtn.style.display = 'none';
|
||||
answerSection.style.display = 'none';
|
||||
|
||||
readQuestionBtn.disabled = false;
|
||||
|
||||
alert('Tossup correct! Now reading the BONUS question for the same team.');
|
||||
}
|
||||
|
||||
// Mark incorrect
|
||||
markIncorrectBtn.addEventListener('click', () => {
|
||||
if (!currentBuzzedPlayer) return;
|
||||
|
||||
database.ref(`gameState/scores/${currentBuzzedPlayer}`).transaction((score) => {
|
||||
return (score || 0) - 4;
|
||||
});
|
||||
const points = isShowingBonus ? 0 : -4;
|
||||
|
||||
// Reactivate buzzers for other players
|
||||
database.ref('gameState').update({
|
||||
buzzerActive: true,
|
||||
buzzer: null,
|
||||
playerAnswer: null
|
||||
});
|
||||
if (points !== 0) {
|
||||
database.ref(`gameState/scores/${currentBuzzedPlayer}`).transaction((score) => {
|
||||
return (score || 0) + points;
|
||||
});
|
||||
}
|
||||
|
||||
answerSection.style.display = 'none';
|
||||
markCorrectBtn.style.display = 'none';
|
||||
markIncorrectBtn.style.display = 'none';
|
||||
showAnswerBtn.style.display = 'none';
|
||||
if (!isShowingBonus) {
|
||||
// Reactivate buzzers for tossup
|
||||
database.ref('gameState').update({
|
||||
buzzerActive: true,
|
||||
buzzer: null
|
||||
});
|
||||
|
||||
answerSection.style.display = 'none';
|
||||
markCorrectBtn.style.display = 'none';
|
||||
markIncorrectBtn.style.display = 'none';
|
||||
showAnswerBtn.style.display = 'none';
|
||||
} else {
|
||||
resetForNextQuestion();
|
||||
}
|
||||
});
|
||||
|
||||
// Show answer
|
||||
@@ -220,9 +426,11 @@ showAnswerBtn.addEventListener('click', () => {
|
||||
|
||||
// Reset game
|
||||
resetGameBtn.addEventListener('click', () => {
|
||||
if (confirm('Reset all scores and start over?')) {
|
||||
if (confirm('Reset all scores and used questions?')) {
|
||||
currentQuestionNumber = 1;
|
||||
usedQuestionIds.clear();
|
||||
speechSynthesis.cancel();
|
||||
stopTimer();
|
||||
database.ref('gameState').set({
|
||||
buzzerActive: false,
|
||||
currentQuestion: null,
|
||||
@@ -237,15 +445,20 @@ resetGameBtn.addEventListener('click', () => {
|
||||
});
|
||||
questionText.textContent = 'Click "New Question" to start';
|
||||
answerSection.style.display = 'none';
|
||||
readQuestionBtn.disabled = true;
|
||||
console.log('Game reset! All questions available again.');
|
||||
}
|
||||
});
|
||||
|
||||
function resetForNextQuestion() {
|
||||
speechSynthesis.cancel();
|
||||
if (speechSynthesis.speaking) {
|
||||
speechSynthesis.cancel();
|
||||
}
|
||||
stopTimer();
|
||||
|
||||
database.ref('gameState').update({
|
||||
buzzerActive: false,
|
||||
buzzer: null,
|
||||
playerAnswer: null,
|
||||
isReading: false
|
||||
});
|
||||
|
||||
@@ -255,5 +468,8 @@ function resetForNextQuestion() {
|
||||
markIncorrectBtn.style.display = 'none';
|
||||
showAnswerBtn.style.display = 'none';
|
||||
answerSection.style.display = 'none';
|
||||
readQuestionBtn.textContent = '🔊 Read Question';
|
||||
readQuestionBtn.disabled = false;
|
||||
currentBuzzedPlayer = null;
|
||||
isShowingBonus = false;
|
||||
}
|
||||
@@ -26,12 +26,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="answer-container" id="answerContainer" style="display: none;">
|
||||
<input type="text" id="answerInput" placeholder="Type your answer...">
|
||||
<button id="submitAnswer" class="submit-btn">Submit Answer</button>
|
||||
</div>
|
||||
|
||||
<div class="question-display listening" id="questionDisplay" style="display: none;">
|
||||
<div class="question-display" id="questionDisplay" style="display: none;">
|
||||
🔊 Listen carefully...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
64
player.js
64
player.js
@@ -4,14 +4,14 @@ const playerId = urlParams.get('id') || '1';
|
||||
const playerColors = ['#ff4444', '#4444ff', '#44ff44', '#ffff44'];
|
||||
const playerNames = ['Player 1', 'Player 2', 'Player 3', 'Player 4'];
|
||||
|
||||
// Buzzer sound frequencies (different for each player)
|
||||
const buzzerFrequencies = [523.25, 659.25, 783.99, 880.00]; // C5, E5, G5, A5
|
||||
|
||||
// DOM elements
|
||||
const playerName = document.getElementById('playerName');
|
||||
const playerScore = document.getElementById('playerScore');
|
||||
const statusMessage = document.getElementById('statusMessage');
|
||||
const buzzer = document.getElementById('buzzer');
|
||||
const answerContainer = document.getElementById('answerContainer');
|
||||
const answerInput = document.getElementById('answerInput');
|
||||
const submitAnswer = document.getElementById('submitAnswer');
|
||||
const questionDisplay = document.getElementById('questionDisplay');
|
||||
|
||||
// Set player identity
|
||||
@@ -23,6 +23,26 @@ document.body.style.setProperty('--player-color', playerColors[playerId - 1]);
|
||||
let canBuzz = false;
|
||||
let hasBuzzed = false;
|
||||
|
||||
// Audio context for buzzer sounds
|
||||
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
|
||||
function playBuzzerSound(playerNum) {
|
||||
const oscillator = audioContext.createOscillator();
|
||||
const gainNode = audioContext.createGain();
|
||||
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
|
||||
oscillator.frequency.value = buzzerFrequencies[playerNum - 1];
|
||||
oscillator.type = 'sine';
|
||||
|
||||
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3);
|
||||
|
||||
oscillator.start(audioContext.currentTime);
|
||||
oscillator.stop(audioContext.currentTime + 0.3);
|
||||
}
|
||||
|
||||
// Listen to game state
|
||||
database.ref('gameState').on('value', (snapshot) => {
|
||||
const state = snapshot.val();
|
||||
@@ -33,7 +53,7 @@ database.ref('gameState').on('value', (snapshot) => {
|
||||
playerScore.textContent = state.scores[`player${playerId}`];
|
||||
}
|
||||
|
||||
// Hide question text - players only hear it
|
||||
// Show question status
|
||||
if (state.isReading) {
|
||||
questionDisplay.textContent = '🔊 Listen to the question...';
|
||||
questionDisplay.style.display = 'block';
|
||||
@@ -51,7 +71,6 @@ database.ref('gameState').on('value', (snapshot) => {
|
||||
buzzer.disabled = false;
|
||||
buzzer.classList.remove('locked');
|
||||
statusMessage.textContent = 'Ready to buzz!';
|
||||
answerContainer.style.display = 'none';
|
||||
} else {
|
||||
canBuzz = false;
|
||||
buzzer.disabled = true;
|
||||
@@ -60,9 +79,8 @@ database.ref('gameState').on('value', (snapshot) => {
|
||||
|
||||
// Check if this player buzzed in
|
||||
if (state.buzzer && state.buzzer.playerId === `player${playerId}`) {
|
||||
statusMessage.textContent = 'You buzzed in! Answer now:';
|
||||
answerContainer.style.display = 'block';
|
||||
answerInput.focus();
|
||||
statusMessage.textContent = '🎯 You buzzed in! Answer verbally to the moderator.';
|
||||
buzzer.classList.add('locked');
|
||||
} else if (state.buzzer && state.buzzer.playerId) {
|
||||
const buzzedPlayerNum = state.buzzer.playerId.replace('player', '');
|
||||
statusMessage.textContent = `Player ${buzzedPlayerNum} buzzed in`;
|
||||
@@ -70,6 +88,15 @@ database.ref('gameState').on('value', (snapshot) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for buzzer events to play sounds
|
||||
database.ref('gameState/buzzer').on('value', (snapshot) => {
|
||||
const buzzer = snapshot.val();
|
||||
if (buzzer && buzzer.playerId) {
|
||||
const playerNum = parseInt(buzzer.playerId.replace('player', ''));
|
||||
playBuzzerSound(playerNum);
|
||||
}
|
||||
});
|
||||
|
||||
// Buzzer click
|
||||
buzzer.addEventListener('click', buzzIn);
|
||||
|
||||
@@ -93,25 +120,4 @@ function buzzIn() {
|
||||
// Visual feedback
|
||||
buzzer.classList.add('buzzed');
|
||||
setTimeout(() => buzzer.classList.remove('buzzed'), 300);
|
||||
}
|
||||
|
||||
// Submit answer
|
||||
submitAnswer.addEventListener('click', submitPlayerAnswer);
|
||||
answerInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') submitPlayerAnswer();
|
||||
});
|
||||
|
||||
function submitPlayerAnswer() {
|
||||
const answer = answerInput.value.trim();
|
||||
if (!answer) return;
|
||||
|
||||
database.ref('gameState/playerAnswer').set({
|
||||
playerId: `player${playerId}`,
|
||||
answer: answer,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
answerInput.value = '';
|
||||
answerContainer.style.display = 'none';
|
||||
statusMessage.textContent = 'Answer submitted! Waiting for moderator...';
|
||||
}
|
||||
65
styles.css
65
styles.css
@@ -372,4 +372,69 @@ margin-top: 15px;
|
||||
}
|
||||
.instructions li {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.timer-display {
|
||||
font-size: 3em;
|
||||
font-weight: bold;
|
||||
color: #ffaa00;
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.setting-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.setting-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.setting-group h3 {
|
||||
margin-bottom: 15px;
|
||||
color: #ffff44;
|
||||
}
|
||||
|
||||
.source-select {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
font-size: 1.1em;
|
||||
border-radius: 8px;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
background: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.source-select option {
|
||||
background: #1a1a2e;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.round-input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
font-size: 1.1em;
|
||||
border-radius: 8px;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
background: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.round-input::placeholder {
|
||||
color: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
.category-filter {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
Reference in New Issue
Block a user