Class_generator/js/games/quiz-game.js
StillHammer 1f8688c4aa Fix WebSocket logging system and add comprehensive network features
- Fix WebSocket server to properly broadcast logs to all connected clients
- Integrate professional logging system with real-time WebSocket interface
- Add network status indicator with DigitalOcean Spaces connectivity
- Implement AWS Signature V4 authentication for private bucket access
- Add JSON content loader with backward compatibility to JS modules
- Restore navigation breadcrumb system with comprehensive logging
- Add multiple content formats: JSON + JS with automatic discovery
- Enhance top bar with logger toggle and network status indicator
- Remove deprecated temp-games module and clean up unused files

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-15 23:05:14 +08:00

355 lines
12 KiB
JavaScript

// === MODULE QUIZ GAME ===
class QuizGame {
constructor(options) {
this.container = options.container;
this.content = options.content;
this.onScoreUpdate = options.onScoreUpdate || (() => {});
this.onGameEnd = options.onGameEnd || (() => {});
// Game state
this.vocabulary = [];
this.currentQuestion = 0;
this.totalQuestions = 10;
this.score = 0;
this.correctAnswers = 0;
this.currentQuestionData = null;
this.hasAnswered = false;
// Extract vocabulary
this.vocabulary = this.extractVocabulary(this.content);
this.init();
}
init() {
// Check if we have enough vocabulary
if (!this.vocabulary || this.vocabulary.length < 4) {
logSh('Not enough vocabulary for Quiz Game', 'ERROR');
this.showInitError();
return;
}
// Adjust total questions based on available vocabulary
this.totalQuestions = Math.min(this.totalQuestions, this.vocabulary.length);
this.createGameInterface();
this.generateQuestion();
}
showInitError() {
this.container.innerHTML = `
<div class="game-error">
<h3>❌ Error loading</h3>
<p>This content doesn't have enough vocabulary for Quiz Game.</p>
<p>The game needs at least 4 vocabulary items.</p>
<button onclick="AppNavigation.goBack()" class="back-btn">← Back</button>
</div>
`;
}
extractVocabulary(content) {
let vocabulary = [];
logSh('📝 Extracting vocabulary from:', content?.name || 'content', 'INFO');
// Use raw module content if available
if (content.rawContent) {
logSh('📦 Using raw module content', 'INFO');
return this.extractVocabularyFromRaw(content.rawContent);
}
// Modern format with contentItems
if (content.contentItems && Array.isArray(content.contentItems)) {
logSh('🆕 ContentItems format detected', 'INFO');
const vocabItems = content.contentItems.filter(item => item.type === 'vocabulary');
if (vocabItems.length > 0) {
vocabulary = vocabItems[0].items || [];
}
}
// Legacy format with vocabulary array
else if (content.vocabulary && Array.isArray(content.vocabulary)) {
logSh('📚 Vocabulary array format detected', 'INFO');
vocabulary = content.vocabulary;
}
return this.finalizeVocabulary(vocabulary);
}
extractVocabularyFromRaw(rawContent) {
logSh('🔧 Extracting from raw content:', rawContent.name || 'Module', 'INFO');
let vocabulary = [];
// Check vocabulary object format (key-value pairs)
if (rawContent.vocabulary && typeof rawContent.vocabulary === 'object' && !Array.isArray(rawContent.vocabulary)) {
vocabulary = Object.entries(rawContent.vocabulary).map(([english, translation]) => ({
english: english,
french: translation
}));
logSh(`📝 ${vocabulary.length} vocabulary pairs extracted from object`, 'INFO');
}
// Check vocabulary array format
else if (rawContent.vocabulary && Array.isArray(rawContent.vocabulary)) {
vocabulary = rawContent.vocabulary;
logSh(`📚 ${vocabulary.length} vocabulary items extracted from array`, 'INFO');
}
return this.finalizeVocabulary(vocabulary);
}
finalizeVocabulary(vocabulary) {
// Filter and validate vocabulary
vocabulary = vocabulary.filter(item =>
item &&
item.english &&
(item.french || item.translation || item.chinese)
).map(item => ({
english: item.english,
french: item.french || item.translation || item.chinese
}));
if (vocabulary.length === 0) {
logSh('❌ No valid vocabulary found', 'ERROR');
// Demo vocabulary as fallback
vocabulary = [
{ english: "cat", french: "chat" },
{ english: "dog", french: "chien" },
{ english: "house", french: "maison" },
{ english: "car", french: "voiture" },
{ english: "book", french: "livre" },
{ english: "water", french: "eau" },
{ english: "food", french: "nourriture" },
{ english: "friend", french: "ami" }
];
logSh('🚨 Using demo vocabulary', 'WARN');
}
// Shuffle vocabulary for random questions
vocabulary = vocabulary.sort(() => Math.random() - 0.5);
logSh(`✅ Quiz Game: ${vocabulary.length} vocabulary items finalized`, 'INFO');
return vocabulary;
}
createGameInterface() {
this.container.innerHTML = `
<div class="quiz-game-wrapper">
<!-- Progress Bar -->
<div class="quiz-progress">
<div class="progress-bar">
<div class="progress-fill" id="progress-fill"></div>
</div>
<div class="progress-text">
<span id="question-counter">1 / ${this.totalQuestions}</span>
<span id="score-display">Score: 0</span>
</div>
</div>
<!-- Question Area -->
<div class="question-area">
<div class="question-text" id="question-text">
Loading question...
</div>
</div>
<!-- Options Area -->
<div class="options-area" id="options-area">
<!-- Options will be generated here -->
</div>
<!-- Controls -->
<div class="quiz-controls">
<button class="control-btn primary" id="next-btn" style="display: none;">Next Question →</button>
<button class="control-btn secondary" id="restart-btn">🔄 Restart</button>
</div>
<!-- Feedback Area -->
<div class="feedback-area" id="feedback-area">
<div class="instruction">
Choose the correct translation!
</div>
</div>
</div>
`;
this.setupEventListeners();
}
setupEventListeners() {
document.getElementById('next-btn').addEventListener('click', () => this.nextQuestion());
document.getElementById('restart-btn').addEventListener('click', () => this.restart());
}
generateQuestion() {
if (this.currentQuestion >= this.totalQuestions) {
this.gameComplete();
return;
}
this.hasAnswered = false;
// Get current vocabulary item
const correctAnswer = this.vocabulary[this.currentQuestion];
// Generate 3 wrong answers from other vocabulary items
const wrongAnswers = this.vocabulary
.filter(item => item !== correctAnswer)
.sort(() => Math.random() - 0.5)
.slice(0, 3)
.map(item => item.french);
// Combine and shuffle all options
const allOptions = [correctAnswer.french, ...wrongAnswers].sort(() => Math.random() - 0.5);
this.currentQuestionData = {
question: correctAnswer.english,
correctAnswer: correctAnswer.french,
options: allOptions
};
this.renderQuestion();
this.updateProgress();
}
renderQuestion() {
const { question, options } = this.currentQuestionData;
// Update question text
document.getElementById('question-text').innerHTML = `
What is the translation of "<strong>${question}</strong>"?
`;
// Clear and generate options
const optionsArea = document.getElementById('options-area');
optionsArea.innerHTML = '';
options.forEach((option, index) => {
const optionButton = document.createElement('button');
optionButton.className = 'quiz-option';
optionButton.textContent = option;
optionButton.addEventListener('click', () => this.selectAnswer(option, optionButton));
optionsArea.appendChild(optionButton);
});
// Hide next button
document.getElementById('next-btn').style.display = 'none';
}
selectAnswer(selectedAnswer, buttonElement) {
if (this.hasAnswered) return;
this.hasAnswered = true;
const isCorrect = selectedAnswer === this.currentQuestionData.correctAnswer;
// Disable all option buttons and show results
const allOptions = document.querySelectorAll('.quiz-option');
allOptions.forEach(btn => {
btn.disabled = true;
if (btn.textContent === this.currentQuestionData.correctAnswer) {
btn.classList.add('correct');
} else if (btn === buttonElement && !isCorrect) {
btn.classList.add('wrong');
} else if (btn !== buttonElement && btn.textContent !== this.currentQuestionData.correctAnswer) {
btn.classList.add('disabled');
}
});
// Update score and feedback
if (isCorrect) {
this.correctAnswers++;
this.score += 10;
this.showFeedback('✅ Correct! Well done!', 'success');
} else {
this.score = Math.max(0, this.score - 5);
this.showFeedback(`❌ Wrong! Correct answer: "${this.currentQuestionData.correctAnswer}"`, 'error');
}
this.updateScore();
// Show next button or finish
if (this.currentQuestion < this.totalQuestions - 1) {
document.getElementById('next-btn').style.display = 'block';
} else {
setTimeout(() => this.gameComplete(), 2000);
}
}
nextQuestion() {
this.currentQuestion++;
this.generateQuestion();
}
updateProgress() {
const progressFill = document.getElementById('progress-fill');
const progressPercent = ((this.currentQuestion + 1) / this.totalQuestions) * 100;
progressFill.style.width = `${progressPercent}%`;
document.getElementById('question-counter').textContent =
`${this.currentQuestion + 1} / ${this.totalQuestions}`;
}
updateScore() {
document.getElementById('score-display').textContent = `Score: ${this.score}`;
this.onScoreUpdate(this.score);
}
gameComplete() {
const accuracy = Math.round((this.correctAnswers / this.totalQuestions) * 100);
// Bonus for high accuracy
if (accuracy >= 90) {
this.score += 50; // Excellence bonus
} else if (accuracy >= 70) {
this.score += 20; // Good performance bonus
}
this.updateScore();
this.showFeedback(
`🎉 Quiz completed! ${this.correctAnswers}/${this.totalQuestions} correct (${accuracy}%)`,
'success'
);
setTimeout(() => {
this.onGameEnd(this.score);
}, 3000);
}
showFeedback(message, type = 'info') {
const feedbackArea = document.getElementById('feedback-area');
feedbackArea.innerHTML = `<div class="instruction ${type}">${message}</div>`;
}
start() {
logSh('❓ Quiz Game: Starting', 'INFO');
this.showFeedback('Choose the correct translation for each word!', 'info');
}
restart() {
logSh('🔄 Quiz Game: Restarting', 'INFO');
this.reset();
this.start();
}
reset() {
this.currentQuestion = 0;
this.score = 0;
this.correctAnswers = 0;
this.hasAnswered = false;
this.currentQuestionData = null;
// Re-shuffle vocabulary
this.vocabulary = this.vocabulary.sort(() => Math.random() - 0.5);
this.generateQuestion();
this.updateScore();
}
destroy() {
this.container.innerHTML = '';
}
}
// Module registration
window.GameModules = window.GameModules || {};
window.GameModules.QuizGame = QuizGame;