diff --git a/display.html b/display.html
index 1ad6acb..a8b7c30 100644
--- a/display.html
+++ b/display.html
@@ -35,12 +35,15 @@
Click "New Question" to start
@@ -64,22 +67,54 @@
-
diff --git a/display.js b/display.js
index c08571a..e066cb5 100644
--- a/display.js
+++ b/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;
}
\ No newline at end of file
diff --git a/player.html b/player.html
index a81f72e..d871c54 100644
--- a/player.html
+++ b/player.html
@@ -26,12 +26,7 @@
-