475 lines
15 KiB
JavaScript
475 lines
15 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 answerSection = document.getElementById('answerSection');
|
|
const buzzedPlayer = document.getElementById('buzzedPlayer');
|
|
const timerDisplay = document.getElementById('timerDisplay');
|
|
|
|
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;
|
|
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({
|
|
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;
|
|
|
|
document.querySelectorAll('.light').forEach(l => l.classList.remove('active'));
|
|
document.getElementById(`light${playerNum}`).classList.add('active');
|
|
|
|
buzzedPlayer.textContent = `Player ${playerNum} buzzed in!`;
|
|
|
|
// Stop reading and timer when someone buzzes
|
|
if (currentUtterance && speechSynthesis.speaking) {
|
|
speechSynthesis.cancel();
|
|
readQuestionBtn.textContent = '🔊 Read Question';
|
|
readQuestionBtn.disabled = false;
|
|
}
|
|
stopTimer();
|
|
|
|
markCorrectBtn.style.display = 'inline-block';
|
|
markIncorrectBtn.style.display = 'inline-block';
|
|
showAnswerBtn.style.display = 'inline-block';
|
|
}
|
|
});
|
|
|
|
// 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 {
|
|
const availableQuestions = getFilteredQuestions();
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Pick a random question from available ones
|
|
const randomIndex = Math.floor(Math.random() * availableQuestions.length);
|
|
currentQuestion = availableQuestions[randomIndex];
|
|
|
|
// Mark this question as used
|
|
usedQuestionIds.add(currentQuestion.id);
|
|
|
|
isShowingBonus = false;
|
|
|
|
console.log('Selected question:', currentQuestion);
|
|
console.log('Questions remaining:', availableQuestions.length - 1);
|
|
|
|
questionText.textContent = currentQuestion.tossup_question;
|
|
questionCategory.textContent = `${currentQuestion.category} - TOSSUP`;
|
|
correctAnswer.textContent = currentQuestion.tossup_answer;
|
|
questionNum.textContent = currentQuestionNumber;
|
|
|
|
// Show source info
|
|
if (currentQuestion.source) {
|
|
questionCategory.textContent = `${currentQuestion.category} - TOSSUP (${currentQuestion.source})`;
|
|
}
|
|
|
|
await database.ref('gameState').update({
|
|
currentQuestion: currentQuestion,
|
|
buzzerActive: false,
|
|
buzzer: null,
|
|
questionNumber: currentQuestionNumber,
|
|
isReading: false
|
|
});
|
|
|
|
// 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';
|
|
showAnswerBtn.style.display = 'none';
|
|
|
|
activateBuzzerBtn.disabled = false;
|
|
readQuestionBtn.disabled = false;
|
|
currentQuestionNumber++;
|
|
|
|
console.log('Question loaded successfully!');
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching question:', error);
|
|
alert('Error loading question. Try refreshing the page.');
|
|
}
|
|
});
|
|
|
|
// Read question aloud
|
|
readQuestionBtn.addEventListener('click', async () => {
|
|
if (!currentQuestion) {
|
|
alert('Load a question first!');
|
|
return;
|
|
}
|
|
|
|
if (!voicesLoaded) {
|
|
await loadVoices();
|
|
}
|
|
|
|
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);
|
|
|
|
database.ref('gameState/isReading').set(true);
|
|
|
|
currentUtterance.onstart = () => {
|
|
readQuestionBtn.textContent = '🔊 Reading...';
|
|
readQuestionBtn.disabled = true;
|
|
};
|
|
|
|
currentUtterance.onend = () => {
|
|
database.ref('gameState/isReading').set(false);
|
|
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);
|
|
});
|
|
|
|
// Activate buzzers
|
|
activateBuzzerBtn.addEventListener('click', () => {
|
|
database.ref('gameState').update({
|
|
buzzerActive: true,
|
|
buzzer: null
|
|
});
|
|
activateBuzzerBtn.disabled = true;
|
|
answerSection.style.display = 'none';
|
|
});
|
|
|
|
// Mark correct
|
|
markCorrectBtn.addEventListener('click', () => {
|
|
if (!currentBuzzedPlayer) return;
|
|
|
|
const points = isShowingBonus ? 10 : 4;
|
|
|
|
database.ref(`gameState/scores/${currentBuzzedPlayer}`).transaction((score) => {
|
|
return (score || 0) + points;
|
|
});
|
|
|
|
// 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;
|
|
|
|
const points = isShowingBonus ? 0 : -4;
|
|
|
|
if (points !== 0) {
|
|
database.ref(`gameState/scores/${currentBuzzedPlayer}`).transaction((score) => {
|
|
return (score || 0) + points;
|
|
});
|
|
}
|
|
|
|
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
|
|
showAnswerBtn.addEventListener('click', () => {
|
|
answerSection.style.display = 'block';
|
|
});
|
|
|
|
// Reset game
|
|
resetGameBtn.addEventListener('click', () => {
|
|
if (confirm('Reset all scores and used questions?')) {
|
|
currentQuestionNumber = 1;
|
|
usedQuestionIds.clear();
|
|
speechSynthesis.cancel();
|
|
stopTimer();
|
|
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';
|
|
readQuestionBtn.disabled = true;
|
|
console.log('Game reset! All questions available again.');
|
|
}
|
|
});
|
|
|
|
function resetForNextQuestion() {
|
|
if (speechSynthesis.speaking) {
|
|
speechSynthesis.cancel();
|
|
}
|
|
stopTimer();
|
|
|
|
database.ref('gameState').update({
|
|
buzzerActive: false,
|
|
buzzer: 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';
|
|
readQuestionBtn.textContent = '🔊 Read Question';
|
|
readQuestionBtn.disabled = false;
|
|
currentBuzzedPlayer = null;
|
|
isShowingBonus = false;
|
|
} |