Files
science-bowl-practice/display.js
2026-01-20 23:26:34 -06:00

279 lines
9.1 KiB
JavaScript

// Use CORS proxy to bypass restrictions
const CORS_PROXY = 'https://corsproxy.io/?';
const API_URL = 'https://scibowldb.com/api/questions/random';
// DOM elements
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 newQuestionBtn = document.getElementById('newQuestion');
const activateBuzzerBtn = document.getElementById('activateBuzzer');
const markCorrectBtn = document.getElementById('markCorrect');
const markIncorrectBtn = document.getElementById('markIncorrect');
const showAnswerBtn = document.getElementById('showAnswer');
const resetGameBtn = document.getElementById('resetGame');
const readQuestionBtn = document.getElementById('readQuestion');
let currentQuestion = null;
let currentQuestionNumber = 1;
let currentBuzzedPlayer = null;
let speechSynthesis = window.speechSynthesis;
let currentUtterance = null;
// Initialize game state
database.ref('gameState').set({
buzzerActive: false,
currentQuestion: null,
scores: {
player1: 0,
player2: 0,
player3: 0,
player4: 0
},
questionNumber: 1,
isReading: false
});
// Listen for game state changes
database.ref('gameState').on('value', (snapshot) => {
const state = snapshot.val();
if (!state) return;
// Update scores
document.getElementById('score1').textContent = state.scores.player1 || 0;
document.getElementById('score2').textContent = state.scores.player2 || 0;
document.getElementById('score3').textContent = state.scores.player3 || 0;
document.getElementById('score4').textContent = state.scores.player4 || 0;
// Update buzzer lights
if (state.buzzer && state.buzzer.playerId) {
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) {
speechSynthesis.cancel();
}
// 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
newQuestionBtn.addEventListener('click', async () => {
const categories = Array.from(document.querySelectorAll('.category-checkbox:checked'))
.map(cb => cb.value);
try {
// Build the request URL with CORS proxy
let requestUrl = CORS_PROXY + encodeURIComponent(API_URL);
// For POST requests with categories, we need a different approach
let fetchOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};
// If categories are selected, use POST through proxy
if (categories.length > 0) {
// Some CORS proxies don't handle POST well, so we'll use GET for now
// and filter on the client side if needed
console.log('Selected categories:', categories);
}
const response = await fetch(requestUrl, fetchOptions);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// If categories were selected, check if question matches
// If not, fetch another one (simple client-side filtering)
if (categories.length > 0 && !categories.includes(data.category)) {
console.log('Question category mismatch, fetching another...');
newQuestionBtn.click(); // Try again
return;
}
currentQuestion = data;
questionText.textContent = currentQuestion.tossup_question;
questionCategory.textContent = currentQuestion.category;
correctAnswer.textContent = currentQuestion.tossup_answer;
questionNum.textContent = currentQuestionNumber;
// Update Firebase
await database.ref('gameState').update({
currentQuestion: currentQuestion,
buzzerActive: false,
buzzer: null,
playerAnswer: null,
questionNumber: currentQuestionNumber,
isReading: false
});
// Reset UI
answerSection.style.display = 'none';
buzzedPlayer.textContent = '';
document.querySelectorAll('.light').forEach(l => l.classList.remove('active'));
markCorrectBtn.style.display = 'none';
markIncorrectBtn.style.display = 'none';
showAnswerBtn.style.display = 'none';
activateBuzzerBtn.disabled = false;
readQuestionBtn.disabled = false;
currentQuestionNumber++;
console.log('Question loaded successfully:', currentQuestion);
} catch (error) {
console.error('Error fetching question:', error);
alert('Error loading question. The CORS proxy might be down. Try again in a moment.');
}
});
// Read question aloud
readQuestionBtn.addEventListener('click', () => {
if (!currentQuestion) {
alert('Load a question first!');
return;
}
// Stop any current speech
speechSynthesis.cancel();
// Create utterance
currentUtterance = new SpeechSynthesisUtterance(currentQuestion.tossup_question);
currentUtterance.rate = 0.9; // Slightly slower for clarity
currentUtterance.pitch = 1;
currentUtterance.volume = 1;
// Update Firebase that we're reading
database.ref('gameState/isReading').set(true);
currentUtterance.onend = () => {
database.ref('gameState/isReading').set(false);
console.log('Finished reading question');
};
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
});
activateBuzzerBtn.disabled = true;
answerSection.style.display = 'none';
});
// Mark correct
markCorrectBtn.addEventListener('click', () => {
if (!currentBuzzedPlayer) return;
database.ref(`gameState/scores/${currentBuzzedPlayer}`).transaction((score) => {
return (score || 0) + 4;
});
resetForNextQuestion();
});
// Mark incorrect
markIncorrectBtn.addEventListener('click', () => {
if (!currentBuzzedPlayer) return;
database.ref(`gameState/scores/${currentBuzzedPlayer}`).transaction((score) => {
return (score || 0) - 4;
});
// Reactivate buzzers for other players
database.ref('gameState').update({
buzzerActive: true,
buzzer: null,
playerAnswer: null
});
answerSection.style.display = 'none';
markCorrectBtn.style.display = 'none';
markIncorrectBtn.style.display = 'none';
showAnswerBtn.style.display = 'none';
});
// Show answer
showAnswerBtn.addEventListener('click', () => {
answerSection.style.display = 'block';
});
// Reset game
resetGameBtn.addEventListener('click', () => {
if (confirm('Reset all scores and start over?')) {
currentQuestionNumber = 1;
speechSynthesis.cancel();
database.ref('gameState').set({
buzzerActive: false,
currentQuestion: null,
scores: {
player1: 0,
player2: 0,
player3: 0,
player4: 0
},
questionNumber: 1,
isReading: false
});
questionText.textContent = 'Click "New Question" to start';
answerSection.style.display = 'none';
}
});
function resetForNextQuestion() {
speechSynthesis.cancel();
database.ref('gameState').update({
buzzerActive: false,
buzzer: null,
playerAnswer: null,
isReading: false
});
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';
currentBuzzedPlayer = null;
}