// === 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;
this.quizDirection = 'original_to_translation'; // 'original_to_translation' or 'translation_to_original'
// Extract vocabulary and additional words from texts/stories
this.vocabulary = this.extractVocabulary(this.content);
this.allWords = this.extractAllWords(this.content);
this.init();
}
init() {
// Check if we have enough vocabulary
if (!this.vocabulary || this.vocabulary.length < 6) {
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 = `
❌ Error loading
This content doesn't have enough vocabulary for Quiz Game.
The game needs at least 6 vocabulary items.
`;
}
extractVocabulary(content) {
let vocabulary = [];
logSh('🔍 Extracting vocabulary from:', content?.name || 'content', 'INFO');
// Priority 1: Use raw module content (simple format)
if (content.rawContent) {
logSh('📦 Using raw module content', 'INFO');
return this.extractVocabularyFromRaw(content.rawContent);
}
// Priority 2: Ultra-modular format (vocabulary object) - ONLY format supported
if (content.vocabulary && typeof content.vocabulary === 'object' && !Array.isArray(content.vocabulary)) {
logSh('✨ Ultra-modular format detected (vocabulary object)', 'INFO');
vocabulary = Object.entries(content.vocabulary).map(([word, data]) => {
// Support ultra-modular format ONLY
if (typeof data === 'object' && data.user_language) {
return {
original: word, // Clé = original_language
translation: data.user_language.split(';')[0], // First translation
fullTranslation: data.user_language, // Complete translation
type: data.type || 'general',
audio: data.audio,
image: data.image,
examples: data.examples,
pronunciation: data.pronunciation,
category: data.type || 'general'
};
}
// Legacy fallback - simple string (temporary, will be removed)
else if (typeof data === 'string') {
return {
original: word,
translation: data.split(';')[0],
fullTranslation: data,
type: 'general',
category: 'general'
};
}
return null;
}).filter(Boolean);
}
// No other formats supported - ultra-modular only
return this.finalizeVocabulary(vocabulary);
}
extractVocabularyFromRaw(rawContent) {
logSh('🔧 Extracting from raw content:', rawContent.name || 'Module', 'INFO');
let vocabulary = [];
// Ultra-modular format (vocabulary object) - ONLY format supported
if (rawContent.vocabulary && typeof rawContent.vocabulary === 'object' && !Array.isArray(rawContent.vocabulary)) {
vocabulary = Object.entries(rawContent.vocabulary).map(([word, data]) => {
// Support ultra-modular format ONLY
if (typeof data === 'object' && data.user_language) {
return {
original: word, // Clé = original_language
translation: data.user_language.split(';')[0], // First translation
fullTranslation: data.user_language, // Complete translation
type: data.type || 'general',
audio: data.audio,
image: data.image,
examples: data.examples,
pronunciation: data.pronunciation,
category: data.type || 'general'
};
}
// Legacy fallback - simple string (temporary, will be removed)
else if (typeof data === 'string') {
return {
original: word,
translation: data.split(';')[0],
fullTranslation: data,
type: 'general',
category: 'general'
};
}
return null;
}).filter(Boolean);
logSh(`✨ ${vocabulary.length} words extracted from ultra-modular vocabulary`, 'INFO');
}
// No other formats supported - ultra-modular only
else {
logSh('⚠️ Content format not supported - ultra-modular format required', 'WARN');
}
return this.finalizeVocabulary(vocabulary);
}
finalizeVocabulary(vocabulary) {
// Validation and cleanup for ultra-modular format
vocabulary = vocabulary.filter(word =>
word &&
typeof word.original === 'string' &&
typeof word.translation === 'string' &&
word.original.trim() !== '' &&
word.translation.trim() !== ''
);
if (vocabulary.length === 0) {
logSh('❌ No valid vocabulary found', 'ERROR');
// Demo vocabulary as last resort
vocabulary = [
{ original: 'hello', translation: 'bonjour', category: 'greetings' },
{ original: 'goodbye', translation: 'au revoir', category: 'greetings' },
{ original: 'thank you', translation: 'merci', category: 'greetings' },
{ original: 'cat', translation: 'chat', category: 'animals' },
{ original: 'dog', translation: 'chien', category: 'animals' },
{ original: 'house', translation: 'maison', category: 'objects' },
{ original: 'car', translation: 'voiture', category: 'objects' },
{ original: 'book', translation: 'livre', category: 'objects' }
];
logSh('🚨 Using demo vocabulary', 'WARN');
}
// Shuffle vocabulary for random questions
vocabulary = this.shuffleArray(vocabulary);
logSh(`✅ Quiz Game: ${vocabulary.length} vocabulary words finalized`, 'INFO');
return vocabulary;
}
extractAllWords(content) {
let allWords = [];
// Add vocabulary words first
allWords = [...this.vocabulary];
// Extract from stories/texts
if (content.rawContent?.story?.chapters) {
content.rawContent.story.chapters.forEach(chapter => {
if (chapter.sentences) {
chapter.sentences.forEach(sentence => {
if (sentence.words && Array.isArray(sentence.words)) {
sentence.words.forEach(wordObj => {
if (wordObj.word && wordObj.translation) {
allWords.push({
original: wordObj.word,
translation: wordObj.translation,
type: wordObj.type || 'word',
pronunciation: wordObj.pronunciation
});
}
});
}
});
}
});
}
// Extract from additional stories (like WTA1B1)
if (content.rawContent?.additionalStories) {
content.rawContent.additionalStories.forEach(story => {
if (story.chapters) {
story.chapters.forEach(chapter => {
if (chapter.sentences) {
chapter.sentences.forEach(sentence => {
if (sentence.words && Array.isArray(sentence.words)) {
sentence.words.forEach(wordObj => {
if (wordObj.word && wordObj.translation) {
allWords.push({
original: wordObj.word,
translation: wordObj.translation,
type: wordObj.type || 'word',
pronunciation: wordObj.pronunciation
});
}
});
}
});
}
});
}
});
}
// Remove duplicates based on original word
const uniqueWords = [];
const seenWords = new Set();
allWords.forEach(word => {
const key = word.original.toLowerCase();
if (!seenWords.has(key)) {
seenWords.add(key);
uniqueWords.push(word);
}
});
logSh(`📚 Extracted ${uniqueWords.length} total words for quiz options`, 'INFO');
return uniqueWords;
}
shuffleArray(array) {
const shuffled = [...array];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}
createGameInterface() {
this.container.innerHTML = `
1 / ${this.totalQuestions}
Score: 0
Choose the correct translation!
`;
// Add CSS for top controls
const style = document.createElement('style');
style.textContent = `
.quiz-top-controls {
position: absolute;
top: 10px;
left: 10px;
z-index: 10;
}
.restart-top {
background: rgba(255, 255, 255, 0.9) !important;
border: 2px solid #ccc !important;
color: #666 !important;
font-size: 12px !important;
padding: 8px 12px !important;
border-radius: 6px !important;
box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important;
}
.restart-top:hover {
background: rgba(255, 255, 255, 1) !important;
border-color: #999 !important;
color: #333 !important;
}
`;
document.head.appendChild(style);
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];
// Randomly choose quiz direction
this.quizDirection = Math.random() < 0.5 ? 'original_to_translation' : 'translation_to_original';
let questionText, correctAnswerText, sourceForWrongAnswers;
if (this.quizDirection === 'original_to_translation') {
questionText = correctAnswer.original;
correctAnswerText = correctAnswer.translation;
sourceForWrongAnswers = 'translation';
} else {
questionText = correctAnswer.translation;
correctAnswerText = correctAnswer.original;
sourceForWrongAnswers = 'original';
}
// Generate 5 wrong answers from allWords (which includes story words)
const availableWords = this.allWords.length >= 6 ? this.allWords : this.vocabulary;
const wrongAnswers = availableWords
.filter(item => item !== correctAnswer)
.sort(() => Math.random() - 0.5)
.slice(0, 5)
.map(item => sourceForWrongAnswers === 'translation' ? item.translation : item.original);
// Combine and shuffle all options (1 correct + 5 wrong = 6 total)
const allOptions = [correctAnswerText, ...wrongAnswers].sort(() => Math.random() - 0.5);
this.currentQuestionData = {
question: questionText,
correctAnswer: correctAnswerText,
options: allOptions,
direction: this.quizDirection
};
this.renderQuestion();
this.updateProgress();
}
renderQuestion() {
const { question, options } = this.currentQuestionData;
// Update question text with direction indicator
const direction = this.currentQuestionData.direction;
const directionText = direction === 'original_to_translation' ?
'What is the translation of' : 'What is the original word for';
document.getElementById('question-text').innerHTML = `
${directionText} "${question}"?
`;
// 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(), 250);
}
}
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 = `${message}
`;
}
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.shuffleArray(this.vocabulary);
this.generateQuestion();
this.updateScore();
}
destroy() {
this.container.innerHTML = '';
}
}
// Module registration
window.GameModules = window.GameModules || {};
window.GameModules.QuizGame = QuizGame;