355 lines
12 KiB
JavaScript
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) {
|
|
console.error('Not enough vocabulary for Quiz Game');
|
|
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 = [];
|
|
|
|
console.log('📝 Extracting vocabulary from:', content?.name || 'content');
|
|
|
|
// Use raw module content if available
|
|
if (content.rawContent) {
|
|
console.log('📦 Using raw module content');
|
|
return this.extractVocabularyFromRaw(content.rawContent);
|
|
}
|
|
|
|
// Modern format with contentItems
|
|
if (content.contentItems && Array.isArray(content.contentItems)) {
|
|
console.log('🆕 ContentItems format detected');
|
|
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)) {
|
|
console.log('📚 Vocabulary array format detected');
|
|
vocabulary = content.vocabulary;
|
|
}
|
|
|
|
return this.finalizeVocabulary(vocabulary);
|
|
}
|
|
|
|
extractVocabularyFromRaw(rawContent) {
|
|
console.log('🔧 Extracting from raw content:', rawContent.name || 'Module');
|
|
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
|
|
}));
|
|
console.log(`📝 ${vocabulary.length} vocabulary pairs extracted from object`);
|
|
}
|
|
// Check vocabulary array format
|
|
else if (rawContent.vocabulary && Array.isArray(rawContent.vocabulary)) {
|
|
vocabulary = rawContent.vocabulary;
|
|
console.log(`📚 ${vocabulary.length} vocabulary items extracted from array`);
|
|
}
|
|
|
|
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) {
|
|
console.error('❌ No valid vocabulary found');
|
|
// 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" }
|
|
];
|
|
console.warn('🚨 Using demo vocabulary');
|
|
}
|
|
|
|
// Shuffle vocabulary for random questions
|
|
vocabulary = vocabulary.sort(() => Math.random() - 0.5);
|
|
|
|
console.log(`✅ Quiz Game: ${vocabulary.length} vocabulary items finalized`);
|
|
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() {
|
|
console.log('❓ Quiz Game: Starting');
|
|
this.showFeedback('Choose the correct translation for each word!', 'info');
|
|
}
|
|
|
|
restart() {
|
|
console.log('🔄 Quiz Game: Restarting');
|
|
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; |