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 class="light player-4" id="light4"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="buzzed-player" id="buzzedPlayer"></div>
|
<div class="buzzed-player" id="buzzedPlayer"></div>
|
||||||
|
<div class="timer-display" id="timerDisplay" style="display: none;">
|
||||||
|
Time: 5s
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="question-section">
|
<div class="question-section">
|
||||||
<div class="question-header">
|
<div class="question-header">
|
||||||
<span class="question-category" id="questionCategory">CATEGORY</span>
|
<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>
|
||||||
<div class="question-text" id="questionText">
|
<div class="question-text" id="questionText">
|
||||||
Click "New Question" to start
|
Click "New Question" to start
|
||||||
@@ -64,22 +67,54 @@
|
|||||||
<button id="resetGame" class="control-btn warning">Reset Game</button>
|
<button id="resetGame" class="control-btn warning">Reset Game</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="category-filter">
|
<div class="settings-section">
|
||||||
<label>
|
<div class="setting-group">
|
||||||
<input type="checkbox" class="category-checkbox" value="PHYSICS" checked> Physics
|
<h3>📚 Source Selection</h3>
|
||||||
</label>
|
<select id="sourceSelect" class="source-select">
|
||||||
<label>
|
<option value="">All Sources (Random)</option>
|
||||||
<input type="checkbox" class="category-checkbox" value="CHEMISTRY" checked> Chemistry
|
<option value="Official">Official Sample Sets</option>
|
||||||
</label>
|
<option value="CSUB">CSUB Regionals</option>
|
||||||
<label>
|
<option value="05Nats">2005 Nationals</option>
|
||||||
<input type="checkbox" class="category-checkbox" value="BIOLOGY" checked> Biology
|
<option value="98Nats">1998 Nationals</option>
|
||||||
</label>
|
</select>
|
||||||
<label>
|
<input type="number" id="roundNumber" placeholder="Round # (optional)" min="1" max="25" class="round-input">
|
||||||
<input type="checkbox" class="category-checkbox" value="MATH" checked> Math
|
</div>
|
||||||
</label>
|
|
||||||
<label>
|
<div class="setting-group">
|
||||||
<input type="checkbox" class="category-checkbox" value="EARTH SCIENCE" checked> Earth Science
|
<h3>🔬 Category Filter</h3>
|
||||||
</label>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
342
display.js
342
display.js
@@ -7,9 +7,9 @@ const questionText = document.getElementById('questionText');
|
|||||||
const questionCategory = document.getElementById('questionCategory');
|
const questionCategory = document.getElementById('questionCategory');
|
||||||
const questionNum = document.getElementById('questionNum');
|
const questionNum = document.getElementById('questionNum');
|
||||||
const correctAnswer = document.getElementById('correctAnswer');
|
const correctAnswer = document.getElementById('correctAnswer');
|
||||||
const playerAnswer = document.getElementById('playerAnswer');
|
|
||||||
const answerSection = document.getElementById('answerSection');
|
const answerSection = document.getElementById('answerSection');
|
||||||
const buzzedPlayer = document.getElementById('buzzedPlayer');
|
const buzzedPlayer = document.getElementById('buzzedPlayer');
|
||||||
|
const timerDisplay = document.getElementById('timerDisplay');
|
||||||
|
|
||||||
const newQuestionBtn = document.getElementById('newQuestion');
|
const newQuestionBtn = document.getElementById('newQuestion');
|
||||||
const activateBuzzerBtn = document.getElementById('activateBuzzer');
|
const activateBuzzerBtn = document.getElementById('activateBuzzer');
|
||||||
@@ -24,6 +24,64 @@ let currentQuestionNumber = 1;
|
|||||||
let currentBuzzedPlayer = null;
|
let currentBuzzedPlayer = null;
|
||||||
let speechSynthesis = window.speechSynthesis;
|
let speechSynthesis = window.speechSynthesis;
|
||||||
let currentUtterance = null;
|
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
|
// Initialize game state
|
||||||
database.ref('gameState').set({
|
database.ref('gameState').set({
|
||||||
@@ -55,63 +113,148 @@ database.ref('gameState').on('value', (snapshot) => {
|
|||||||
const playerNum = state.buzzer.playerId.replace('player', '');
|
const playerNum = state.buzzer.playerId.replace('player', '');
|
||||||
currentBuzzedPlayer = state.buzzer.playerId;
|
currentBuzzedPlayer = state.buzzer.playerId;
|
||||||
|
|
||||||
// Light up the buzzer
|
|
||||||
document.querySelectorAll('.light').forEach(l => l.classList.remove('active'));
|
document.querySelectorAll('.light').forEach(l => l.classList.remove('active'));
|
||||||
document.getElementById(`light${playerNum}`).classList.add('active');
|
document.getElementById(`light${playerNum}`).classList.add('active');
|
||||||
|
|
||||||
buzzedPlayer.textContent = `Player ${playerNum} buzzed in!`;
|
buzzedPlayer.textContent = `Player ${playerNum} buzzed in!`;
|
||||||
|
|
||||||
// Stop reading when someone buzzes
|
// Stop reading and timer when someone buzzes
|
||||||
if (currentUtterance) {
|
if (currentUtterance && speechSynthesis.speaking) {
|
||||||
speechSynthesis.cancel();
|
speechSynthesis.cancel();
|
||||||
|
readQuestionBtn.textContent = '🔊 Read Question';
|
||||||
|
readQuestionBtn.disabled = false;
|
||||||
}
|
}
|
||||||
|
stopTimer();
|
||||||
|
|
||||||
// Show grading buttons
|
|
||||||
markCorrectBtn.style.display = 'inline-block';
|
markCorrectBtn.style.display = 'inline-block';
|
||||||
markIncorrectBtn.style.display = 'inline-block';
|
markIncorrectBtn.style.display = 'inline-block';
|
||||||
showAnswerBtn.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 () => {
|
newQuestionBtn.addEventListener('click', async () => {
|
||||||
const categories = Array.from(document.querySelectorAll('.category-checkbox:checked'))
|
const categories = Array.from(document.querySelectorAll('.category-checkbox:checked'))
|
||||||
.map(cb => cb.value);
|
.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 {
|
try {
|
||||||
// Just fetch a random question without filtering
|
const availableQuestions = getFilteredQuestions();
|
||||||
const response = await fetch(CORS_PROXY + encodeURIComponent(API_URL), {
|
|
||||||
method: 'GET' // Changed to GET - simpler
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (availableQuestions.length === 0) {
|
||||||
throw new Error('API request failed');
|
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
|
// Mark this question as used
|
||||||
currentQuestion = data;
|
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;
|
questionText.textContent = currentQuestion.tossup_question;
|
||||||
questionCategory.textContent = currentQuestion.category;
|
questionCategory.textContent = `${currentQuestion.category} - TOSSUP`;
|
||||||
correctAnswer.textContent = currentQuestion.tossup_answer;
|
correctAnswer.textContent = currentQuestion.tossup_answer;
|
||||||
questionNum.textContent = currentQuestionNumber;
|
questionNum.textContent = currentQuestionNumber;
|
||||||
|
|
||||||
// Update Firebase
|
// Show source info
|
||||||
|
if (currentQuestion.source) {
|
||||||
|
questionCategory.textContent = `${currentQuestion.category} - TOSSUP (${currentQuestion.source})`;
|
||||||
|
}
|
||||||
|
|
||||||
await database.ref('gameState').update({
|
await database.ref('gameState').update({
|
||||||
currentQuestion: currentQuestion,
|
currentQuestion: currentQuestion,
|
||||||
buzzerActive: false,
|
buzzerActive: false,
|
||||||
buzzer: null,
|
buzzer: null,
|
||||||
playerAnswer: null,
|
|
||||||
questionNumber: currentQuestionNumber,
|
questionNumber: currentQuestionNumber,
|
||||||
isReading: false
|
isReading: false
|
||||||
});
|
});
|
||||||
@@ -119,6 +262,7 @@ newQuestionBtn.addEventListener('click', async () => {
|
|||||||
// Reset UI
|
// Reset UI
|
||||||
answerSection.style.display = 'none';
|
answerSection.style.display = 'none';
|
||||||
buzzedPlayer.textContent = '';
|
buzzedPlayer.textContent = '';
|
||||||
|
timerDisplay.style.display = 'none';
|
||||||
document.querySelectorAll('.light').forEach(l => l.classList.remove('active'));
|
document.querySelectorAll('.light').forEach(l => l.classList.remove('active'));
|
||||||
markCorrectBtn.style.display = 'none';
|
markCorrectBtn.style.display = 'none';
|
||||||
markIncorrectBtn.style.display = 'none';
|
markIncorrectBtn.style.display = 'none';
|
||||||
@@ -132,50 +276,78 @@ newQuestionBtn.addEventListener('click', async () => {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching question:', 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
|
// Read question aloud
|
||||||
readQuestionBtn.addEventListener('click', () => {
|
readQuestionBtn.addEventListener('click', async () => {
|
||||||
if (!currentQuestion) {
|
if (!currentQuestion) {
|
||||||
alert('Load a question first!');
|
alert('Load a question first!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop any current speech
|
if (!voicesLoaded) {
|
||||||
speechSynthesis.cancel();
|
await loadVoices();
|
||||||
|
}
|
||||||
|
|
||||||
// Create utterance
|
if (speechSynthesis.speaking) {
|
||||||
currentUtterance = new SpeechSynthesisUtterance(currentQuestion.tossup_question);
|
speechSynthesis.cancel();
|
||||||
currentUtterance.rate = 0.9; // Slightly slower for clarity
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
currentUtterance.pitch = 1;
|
}
|
||||||
currentUtterance.volume = 1;
|
|
||||||
|
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);
|
database.ref('gameState/isReading').set(true);
|
||||||
|
|
||||||
|
currentUtterance.onstart = () => {
|
||||||
|
readQuestionBtn.textContent = '🔊 Reading...';
|
||||||
|
readQuestionBtn.disabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
currentUtterance.onend = () => {
|
currentUtterance.onend = () => {
|
||||||
database.ref('gameState/isReading').set(false);
|
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);
|
speechSynthesis.speak(currentUtterance);
|
||||||
readQuestionBtn.textContent = '🔊 Reading...';
|
|
||||||
readQuestionBtn.disabled = true;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
readQuestionBtn.textContent = '🔊 Read Question';
|
|
||||||
readQuestionBtn.disabled = false;
|
|
||||||
}, 2000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Activate buzzers
|
// Activate buzzers
|
||||||
activateBuzzerBtn.addEventListener('click', () => {
|
activateBuzzerBtn.addEventListener('click', () => {
|
||||||
database.ref('gameState').update({
|
database.ref('gameState').update({
|
||||||
buzzerActive: true,
|
buzzerActive: true,
|
||||||
buzzer: null,
|
buzzer: null
|
||||||
playerAnswer: null
|
|
||||||
});
|
});
|
||||||
activateBuzzerBtn.disabled = true;
|
activateBuzzerBtn.disabled = true;
|
||||||
answerSection.style.display = 'none';
|
answerSection.style.display = 'none';
|
||||||
@@ -185,32 +357,66 @@ activateBuzzerBtn.addEventListener('click', () => {
|
|||||||
markCorrectBtn.addEventListener('click', () => {
|
markCorrectBtn.addEventListener('click', () => {
|
||||||
if (!currentBuzzedPlayer) return;
|
if (!currentBuzzedPlayer) return;
|
||||||
|
|
||||||
|
const points = isShowingBonus ? 10 : 4;
|
||||||
|
|
||||||
database.ref(`gameState/scores/${currentBuzzedPlayer}`).transaction((score) => {
|
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
|
// Mark incorrect
|
||||||
markIncorrectBtn.addEventListener('click', () => {
|
markIncorrectBtn.addEventListener('click', () => {
|
||||||
if (!currentBuzzedPlayer) return;
|
if (!currentBuzzedPlayer) return;
|
||||||
|
|
||||||
database.ref(`gameState/scores/${currentBuzzedPlayer}`).transaction((score) => {
|
const points = isShowingBonus ? 0 : -4;
|
||||||
return (score || 0) - 4;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reactivate buzzers for other players
|
if (points !== 0) {
|
||||||
database.ref('gameState').update({
|
database.ref(`gameState/scores/${currentBuzzedPlayer}`).transaction((score) => {
|
||||||
buzzerActive: true,
|
return (score || 0) + points;
|
||||||
buzzer: null,
|
});
|
||||||
playerAnswer: null
|
}
|
||||||
});
|
|
||||||
|
|
||||||
answerSection.style.display = 'none';
|
if (!isShowingBonus) {
|
||||||
markCorrectBtn.style.display = 'none';
|
// Reactivate buzzers for tossup
|
||||||
markIncorrectBtn.style.display = 'none';
|
database.ref('gameState').update({
|
||||||
showAnswerBtn.style.display = 'none';
|
buzzerActive: true,
|
||||||
|
buzzer: null
|
||||||
|
});
|
||||||
|
|
||||||
|
answerSection.style.display = 'none';
|
||||||
|
markCorrectBtn.style.display = 'none';
|
||||||
|
markIncorrectBtn.style.display = 'none';
|
||||||
|
showAnswerBtn.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
resetForNextQuestion();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show answer
|
// Show answer
|
||||||
@@ -220,9 +426,11 @@ showAnswerBtn.addEventListener('click', () => {
|
|||||||
|
|
||||||
// Reset game
|
// Reset game
|
||||||
resetGameBtn.addEventListener('click', () => {
|
resetGameBtn.addEventListener('click', () => {
|
||||||
if (confirm('Reset all scores and start over?')) {
|
if (confirm('Reset all scores and used questions?')) {
|
||||||
currentQuestionNumber = 1;
|
currentQuestionNumber = 1;
|
||||||
|
usedQuestionIds.clear();
|
||||||
speechSynthesis.cancel();
|
speechSynthesis.cancel();
|
||||||
|
stopTimer();
|
||||||
database.ref('gameState').set({
|
database.ref('gameState').set({
|
||||||
buzzerActive: false,
|
buzzerActive: false,
|
||||||
currentQuestion: null,
|
currentQuestion: null,
|
||||||
@@ -237,15 +445,20 @@ resetGameBtn.addEventListener('click', () => {
|
|||||||
});
|
});
|
||||||
questionText.textContent = 'Click "New Question" to start';
|
questionText.textContent = 'Click "New Question" to start';
|
||||||
answerSection.style.display = 'none';
|
answerSection.style.display = 'none';
|
||||||
|
readQuestionBtn.disabled = true;
|
||||||
|
console.log('Game reset! All questions available again.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function resetForNextQuestion() {
|
function resetForNextQuestion() {
|
||||||
speechSynthesis.cancel();
|
if (speechSynthesis.speaking) {
|
||||||
|
speechSynthesis.cancel();
|
||||||
|
}
|
||||||
|
stopTimer();
|
||||||
|
|
||||||
database.ref('gameState').update({
|
database.ref('gameState').update({
|
||||||
buzzerActive: false,
|
buzzerActive: false,
|
||||||
buzzer: null,
|
buzzer: null,
|
||||||
playerAnswer: null,
|
|
||||||
isReading: false
|
isReading: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -255,5 +468,8 @@ function resetForNextQuestion() {
|
|||||||
markIncorrectBtn.style.display = 'none';
|
markIncorrectBtn.style.display = 'none';
|
||||||
showAnswerBtn.style.display = 'none';
|
showAnswerBtn.style.display = 'none';
|
||||||
answerSection.style.display = 'none';
|
answerSection.style.display = 'none';
|
||||||
|
readQuestionBtn.textContent = '🔊 Read Question';
|
||||||
|
readQuestionBtn.disabled = false;
|
||||||
currentBuzzedPlayer = null;
|
currentBuzzedPlayer = null;
|
||||||
|
isShowingBonus = false;
|
||||||
}
|
}
|
||||||
@@ -26,12 +26,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="answer-container" id="answerContainer" style="display: none;">
|
<div class="question-display" id="questionDisplay" 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;">
|
|
||||||
🔊 Listen carefully...
|
🔊 Listen carefully...
|
||||||
</div>
|
</div>
|
||||||
</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 playerColors = ['#ff4444', '#4444ff', '#44ff44', '#ffff44'];
|
||||||
const playerNames = ['Player 1', 'Player 2', 'Player 3', 'Player 4'];
|
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
|
// DOM elements
|
||||||
const playerName = document.getElementById('playerName');
|
const playerName = document.getElementById('playerName');
|
||||||
const playerScore = document.getElementById('playerScore');
|
const playerScore = document.getElementById('playerScore');
|
||||||
const statusMessage = document.getElementById('statusMessage');
|
const statusMessage = document.getElementById('statusMessage');
|
||||||
const buzzer = document.getElementById('buzzer');
|
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');
|
const questionDisplay = document.getElementById('questionDisplay');
|
||||||
|
|
||||||
// Set player identity
|
// Set player identity
|
||||||
@@ -23,6 +23,26 @@ document.body.style.setProperty('--player-color', playerColors[playerId - 1]);
|
|||||||
let canBuzz = false;
|
let canBuzz = false;
|
||||||
let hasBuzzed = 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
|
// Listen to game state
|
||||||
database.ref('gameState').on('value', (snapshot) => {
|
database.ref('gameState').on('value', (snapshot) => {
|
||||||
const state = snapshot.val();
|
const state = snapshot.val();
|
||||||
@@ -33,7 +53,7 @@ database.ref('gameState').on('value', (snapshot) => {
|
|||||||
playerScore.textContent = state.scores[`player${playerId}`];
|
playerScore.textContent = state.scores[`player${playerId}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide question text - players only hear it
|
// Show question status
|
||||||
if (state.isReading) {
|
if (state.isReading) {
|
||||||
questionDisplay.textContent = '🔊 Listen to the question...';
|
questionDisplay.textContent = '🔊 Listen to the question...';
|
||||||
questionDisplay.style.display = 'block';
|
questionDisplay.style.display = 'block';
|
||||||
@@ -51,7 +71,6 @@ database.ref('gameState').on('value', (snapshot) => {
|
|||||||
buzzer.disabled = false;
|
buzzer.disabled = false;
|
||||||
buzzer.classList.remove('locked');
|
buzzer.classList.remove('locked');
|
||||||
statusMessage.textContent = 'Ready to buzz!';
|
statusMessage.textContent = 'Ready to buzz!';
|
||||||
answerContainer.style.display = 'none';
|
|
||||||
} else {
|
} else {
|
||||||
canBuzz = false;
|
canBuzz = false;
|
||||||
buzzer.disabled = true;
|
buzzer.disabled = true;
|
||||||
@@ -60,9 +79,8 @@ database.ref('gameState').on('value', (snapshot) => {
|
|||||||
|
|
||||||
// Check if this player buzzed in
|
// Check if this player buzzed in
|
||||||
if (state.buzzer && state.buzzer.playerId === `player${playerId}`) {
|
if (state.buzzer && state.buzzer.playerId === `player${playerId}`) {
|
||||||
statusMessage.textContent = 'You buzzed in! Answer now:';
|
statusMessage.textContent = '🎯 You buzzed in! Answer verbally to the moderator.';
|
||||||
answerContainer.style.display = 'block';
|
buzzer.classList.add('locked');
|
||||||
answerInput.focus();
|
|
||||||
} else if (state.buzzer && state.buzzer.playerId) {
|
} else if (state.buzzer && state.buzzer.playerId) {
|
||||||
const buzzedPlayerNum = state.buzzer.playerId.replace('player', '');
|
const buzzedPlayerNum = state.buzzer.playerId.replace('player', '');
|
||||||
statusMessage.textContent = `Player ${buzzedPlayerNum} buzzed in`;
|
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 click
|
||||||
buzzer.addEventListener('click', buzzIn);
|
buzzer.addEventListener('click', buzzIn);
|
||||||
|
|
||||||
@@ -93,25 +120,4 @@ function buzzIn() {
|
|||||||
// Visual feedback
|
// Visual feedback
|
||||||
buzzer.classList.add('buzzed');
|
buzzer.classList.add('buzzed');
|
||||||
setTimeout(() => buzzer.classList.remove('buzzed'), 300);
|
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 {
|
.instructions li {
|
||||||
margin: 10px 0;
|
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