// === GRAMMAR DISCOVERY GAME ===
// Interactive game for discovering and learning grammar patterns
class GrammarDiscovery {
constructor({ container, content, onScoreUpdate, onGameEnd }) {
this.container = container;
this.content = content;
this.onScoreUpdate = onScoreUpdate;
this.onGameEnd = onGameEnd;
this.score = 0;
// ROTATION SYSTEM FOR FOCUSED CONCEPT LEARNING
this.rotationSteps = [
'explanation-basic', // 1. Explication de base (langue originale)
'examples-simple', // 2. Exemples simples
'exercise-basic', // 3. Exercices de base
'explanation-detailed', // 4. Explication détaillée
'examples-complex', // 5. Exemples complexes
'exercise-intermediate', // 6. Exercices intermédiaires
'summary', // 7. Résumé du concept
'exercise-global' // 8. Exercice global final
];
this.currentStep = 0;
this.grammarConcept = null; // Single focused concept (user selected)
this.conceptData = {};
this.stepProgress = {};
this.availableConcepts = []; // All available grammar concepts
this.conceptSelected = false; // Whether user has chosen a concept
this.injectCSS();
this.extractAvailableConcepts();
this.init();
}
injectCSS() {
if (document.getElementById('grammar-discovery-styles')) return;
const styleSheet = document.createElement('style');
styleSheet.id = 'grammar-discovery-styles';
styleSheet.textContent = `
.grammar-discovery {
display: flex;
flex-direction: column;
height: 100%;
font-family: 'Arial', sans-serif;
}
.grammar-hud {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 10px 10px 0 0;
}
.grammar-phase {
font-size: 18px;
font-weight: bold;
display: flex;
align-items: center;
gap: 10px;
}
.phase-icon {
font-size: 24px;
}
.grammar-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 20px;
background: linear-gradient(145deg, #f8f9ff, #e6e9ff);
overflow-y: auto;
}
.rule-card {
background: white;
border-radius: 15px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.1);
border: 2px solid transparent;
transition: all 0.3s ease;
}
.rule-card.active {
border-color: #667eea;
transform: translateY(-2px);
box-shadow: 0 12px 40px rgba(102, 126, 234, 0.2);
}
.rule-title {
font-size: 24px;
font-weight: bold;
color: #4c51bf;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.rule-explanation {
font-size: 16px;
line-height: 1.6;
color: #4a5568;
margin-bottom: 20px;
background: #f7fafc;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.examples-section {
margin-top: 20px;
}
.example-item {
background: #ffffff;
border: 2px solid #e2e8f0;
border-radius: 12px;
padding: 20px;
margin-bottom: 15px;
transition: all 0.3s ease;
cursor: pointer;
}
.example-item:hover {
border-color: #667eea;
transform: translateX(5px);
}
.example-item.revealed {
border-color: #48bb78;
background: linear-gradient(135deg, #f0fff4, #c6f6d5);
}
.chinese-text {
font-size: 22px;
font-weight: bold;
color: #2d3748;
margin-bottom: 8px;
}
.english-text {
font-size: 18px;
color: #4a5568;
margin-bottom: 8px;
}
.pronunciation {
font-size: 16px;
color: #718096;
font-style: italic;
margin-bottom: 10px;
}
.explanation-text {
font-size: 14px;
color: #667eea;
background: #edf2f7;
padding: 10px;
border-radius: 6px;
display: none;
}
.example-item.revealed .explanation-text {
display: block;
animation: fadeIn 0.5s ease;
}
.discovery-controls {
display: flex;
gap: 15px;
margin-top: 20px;
justify-content: center;
}
.discover-btn {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
padding: 12px 25px;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
}
.discover-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.discover-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.practice-question {
background: white;
border-radius: 15px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.1);
}
.question-text {
font-size: 20px;
color: #2d3748;
margin-bottom: 20px;
line-height: 1.6;
}
.options-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.option-btn {
background: white;
border: 2px solid #e2e8f0;
border-radius: 12px;
padding: 15px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
}
.option-btn:hover {
border-color: #667eea;
background: #f7fafc;
}
.option-btn.correct {
border-color: #48bb78;
background: linear-gradient(135deg, #f0fff4, #c6f6d5);
color: #22543d;
}
.option-btn.incorrect {
border-color: #f56565;
background: linear-gradient(135deg, #fed7d7, #feb2b2);
color: #742a2a;
}
.feedback {
background: #f7fafc;
border-radius: 10px;
padding: 15px;
margin-top: 15px;
border-left: 4px solid #667eea;
display: none;
}
.feedback.show {
display: block;
animation: slideIn 0.3s ease;
}
.progress-bar {
background: #e2e8f0;
height: 6px;
border-radius: 3px;
margin: 10px 0;
overflow: hidden;
}
.progress-fill {
background: linear-gradient(90deg, #667eea, #764ba2);
height: 100%;
border-radius: 3px;
transition: width 0.5s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideIn {
from { opacity: 0; transform: translateX(-20px); }
to { opacity: 1; transform: translateX(0); }
}
.concept-selector {
background: white;
border-radius: 15px;
padding: 30px;
margin: 20px 0;
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.1);
text-align: center;
}
.selector-title {
font-size: 24px;
font-weight: bold;
color: #4c51bf;
margin-bottom: 20px;
}
.selector-description {
font-size: 16px;
color: #4a5568;
margin-bottom: 25px;
line-height: 1.6;
}
.concept-dropdown {
width: 100%;
max-width: 400px;
padding: 15px;
font-size: 16px;
border: 2px solid #e2e8f0;
border-radius: 10px;
background: white;
margin-bottom: 20px;
cursor: pointer;
transition: all 0.3s ease;
}
.concept-dropdown:hover {
border-color: #667eea;
}
.concept-dropdown:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.concept-preview {
background: #f7fafc;
border-radius: 10px;
padding: 20px;
margin: 20px 0;
text-align: left;
display: none;
}
.concept-preview.show {
display: block;
animation: fadeIn 0.3s ease;
}
.preview-title {
font-size: 18px;
font-weight: bold;
color: #2d3748;
margin-bottom: 10px;
}
.preview-explanation {
font-size: 14px;
color: #4a5568;
margin-bottom: 15px;
}
.preview-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 10px;
font-size: 12px;
color: #718096;
}
.stat-item {
background: white;
padding: 8px;
border-radius: 6px;
text-align: center;
}
.phase-complete {
text-align: center;
padding: 40px;
background: linear-gradient(135deg, #f0fff4, #c6f6d5);
border-radius: 15px;
margin: 20px 0;
}
.complete-icon {
font-size: 48px;
margin-bottom: 20px;
}
.complete-title {
font-size: 24px;
font-weight: bold;
color: #22543d;
margin-bottom: 15px;
}
.tts-controls {
display: flex;
gap: 10px;
align-items: center;
margin-top: 10px;
}
.tts-btn {
background: #667eea;
color: white;
border: none;
border-radius: 20px;
padding: 8px 15px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s ease;
}
.tts-btn:hover {
background: #5a67d8;
transform: scale(1.05);
}
`;
document.head.appendChild(styleSheet);
}
extractAvailableConcepts() {
if (!this.content || !this.content.grammar) {
console.error('No grammar content found for Grammar Discovery game');
return;
}
// GET ALL AVAILABLE CONCEPTS for selection
this.availableConcepts = Object.entries(this.content.grammar).map(([key, conceptData]) => ({
id: key,
title: conceptData.title,
explanation: conceptData.explanation,
data: conceptData
}));
console.log(`🎯 Found ${this.availableConcepts.length} grammar concepts available for selection`);
}
selectConcept(conceptId) {
const selectedConcept = this.availableConcepts.find(c => c.id === conceptId);
if (!selectedConcept) {
console.error('Concept not found:', conceptId);
return;
}
this.grammarConcept = conceptId;
this.conceptData = selectedConcept.data;
this.conceptSelected = true;
console.log(`🎯 Selected concept: ${this.grammarConcept}`);
// ORGANIZE CONTENT BY DIFFICULTY LEVELS
this.organizeConceptContent();
// Start the rotation cycle
this.startRotationCycle();
}
organizeConceptContent() {
const concept = this.conceptData;
// BASIC EXAMPLES (first 2-3 simple ones)
this.simpleExamples = [];
this.complexExamples = [];
if (concept.examples) {
this.simpleExamples = concept.examples.slice(0, 3);
this.complexExamples = concept.examples.slice(3);
}
// Get examples from detailed explanation sections
if (concept.detailedExplanation) {
Object.values(concept.detailedExplanation).forEach(section => {
if (section.examples) {
// First 2 go to simple, rest to complex
const sectionSimple = section.examples.slice(0, 2);
const sectionComplex = section.examples.slice(2);
this.simpleExamples.push(...sectionSimple);
this.complexExamples.push(...sectionComplex);
}
});
}
// ORGANIZE EXERCISES BY DIFFICULTY
this.basicExercises = [];
this.intermediateExercises = [];
this.globalExercises = [];
if (this.content.fillInBlanks) {
this.content.fillInBlanks.forEach(exercise => {
// Accept all fillInBlanks for focused grammar lesson content
const isGrammarLesson = this.content.type === 'grammar_course';
const isRelevant = isGrammarLesson ||
exercise.grammarFocus === this.grammarConcept ||
exercise.grammarFocus === 'completion' ||
exercise.grammarFocus === 'change-of-state';
if (isRelevant) {
// Determine difficulty by sentence complexity
if (exercise.sentence.length < 15) {
this.basicExercises.push(exercise);
} else {
this.intermediateExercises.push(exercise);
}
}
});
}
if (this.content.corrections) {
this.content.corrections.forEach(correction => {
// Accept all corrections for focused grammar lesson content
const isGrammarLesson = this.content.type === 'grammar_course';
const isRelevant = isGrammarLesson ||
correction.grammarFocus === this.grammarConcept;
if (isRelevant) {
this.intermediateExercises.push({
type: 'correction',
question: 'Which sentence is correct?',
options: [correction.correct, correction.incorrect],
correctAnswer: correction.correct,
explanation: correction.explanation
});
}
});
}
// Global exercises combine multiple concepts
this.globalExercises = [...this.basicExercises, ...this.intermediateExercises];
this.globalExercises = this.shuffleArray(this.globalExercises).slice(0, 5);
console.log(`📊 Content organized:
- Simple examples: ${this.simpleExamples.length}
- Complex examples: ${this.complexExamples.length}
- Basic exercises: ${this.basicExercises.length}
- Intermediate exercises: ${this.intermediateExercises.length}
- Global exercises: ${this.globalExercises.length}`);
}
init() {
this.container.innerHTML = `
🎯
Select Grammar Concept
Step 1 of ${this.rotationSteps.length}
Score: 0
`;
this.showConceptSelector();
}
showConceptSelector() {
if (this.availableConcepts.length === 0) {
console.error('No concepts available for selection');
return;
}
const contentDiv = document.getElementById('grammar-content');
contentDiv.innerHTML = `
🎯 Choose Grammar Concept
Select which grammar concept you want to focus on for intensive study with our 8-step rotation system.
`;
// Store reference for global access
window.currentGrammarGame = this;
}
previewConcept(conceptId) {
const previewDiv = document.getElementById('concept-preview');
const startBtn = document.getElementById('start-btn');
if (!conceptId) {
previewDiv.classList.remove('show');
startBtn.disabled = true;
return;
}
const concept = this.availableConcepts.find(c => c.id === conceptId);
if (!concept) return;
// Calculate stats for this concept
this.grammarConcept = conceptId;
this.conceptData = concept.data;
this.organizeConceptContent();
previewDiv.innerHTML = `
${concept.title}
${concept.explanation}
${this.simpleExamples.length}
Simple Examples
${this.complexExamples.length}
Complex Examples
${this.basicExercises.length}
Basic Exercises
${this.intermediateExercises.length}
Intermediate Exercises
${this.globalExercises.length}
Final Test Questions
`;
previewDiv.classList.add('show');
startBtn.disabled = false;
}
startSelectedConcept() {
const dropdown = document.getElementById('concept-dropdown');
const selectedConceptId = dropdown.value;
if (!selectedConceptId) {
alert('Please select a grammar concept first!');
return;
}
// Update UI to show rotation progress
document.getElementById('step-progress').style.display = 'block';
document.getElementById('phase-icon').textContent = '📚';
document.getElementById('phase-text').textContent = 'Basic Explanation';
this.selectConcept(selectedConceptId);
}
startRotationCycle() {
this.currentStep = 0;
this.showCurrentStep();
}
showCurrentStep() {
const stepType = this.rotationSteps[this.currentStep];
const stepNumber = this.currentStep + 1;
document.getElementById('current-step').textContent = stepNumber;
// Update phase icon and text based on step
const phaseInfo = this.getPhaseInfo(stepType);
document.getElementById('phase-icon').textContent = phaseInfo.icon;
document.getElementById('phase-text').textContent = phaseInfo.text;
// Store reference for global access
window.currentGrammarGame = this;
// Show content for current step
switch (stepType) {
case 'explanation-basic':
this.showBasicExplanation();
break;
case 'examples-simple':
this.showSimpleExamples();
break;
case 'exercise-basic':
this.showBasicExercises();
break;
case 'explanation-detailed':
this.showDetailedExplanation();
break;
case 'examples-complex':
this.showComplexExamples();
break;
case 'exercise-intermediate':
this.showIntermediateExercises();
break;
case 'summary':
this.showSummary();
break;
case 'exercise-global':
this.showGlobalExercises();
break;
}
}
getPhaseInfo(stepType) {
const phaseMap = {
'explanation-basic': { icon: '📚', text: 'Basic Explanation' },
'examples-simple': { icon: '💡', text: 'Simple Examples' },
'exercise-basic': { icon: '✏️', text: 'Basic Practice' },
'explanation-detailed': { icon: '🔍', text: 'Detailed Explanation' },
'examples-complex': { icon: '🧩', text: 'Complex Examples' },
'exercise-intermediate': { icon: '💪', text: 'Intermediate Practice' },
'summary': { icon: '📝', text: 'Summary' },
'exercise-global': { icon: '🏆', text: 'Final Test' }
};
return phaseMap[stepType] || { icon: '❓', text: 'Unknown' };
}
nextStep() {
this.currentStep++;
if (this.currentStep >= this.rotationSteps.length) {
this.completeRotation();
} else {
this.showCurrentStep();
}
}
completeRotation() {
const contentDiv = document.getElementById('grammar-content');
contentDiv.innerHTML = `
🎉
Grammar Concept Mastered!
You've completed the full rotation for: ${this.conceptData.title}
Final Score: ${this.score}
`;
this.onGameEnd(this.score);
}
// === ROTATION STEP IMPLEMENTATIONS ===
showBasicExplanation() {
const concept = this.conceptData;
const contentDiv = document.getElementById('grammar-content');
contentDiv.innerHTML = `
📚 ${concept.title}
${concept.explanation}
${concept.mainRules ? `
🎯 Key Rules:
${concept.mainRules.slice(0, 3).map(rule => `- ${rule}
`).join('')}
` : ''}
`;
}
showSimpleExamples() {
const contentDiv = document.getElementById('grammar-content');
contentDiv.innerHTML = `
💡 Simple Examples
${this.simpleExamples.map((example, index) => `
${example.chinese}
${example.english}
${example.pronunciation || ''}
${example.explanation || example.breakdown || ''}
`).join('')}
`;
// Auto-play first example
if (this.simpleExamples.length > 0) {
setTimeout(() => {
this.speakChinese(this.simpleExamples[0].chinese);
}, 1000);
}
}
showBasicExercises() {
if (this.basicExercises.length === 0) {
this.nextStep();
return;
}
this.currentExerciseSet = this.basicExercises;
this.currentExerciseIndex = 0;
this.showExercise('basic');
}
showDetailedExplanation() {
const concept = this.conceptData;
const contentDiv = document.getElementById('grammar-content');
let detailsHtml = '';
if (concept.detailedExplanation) {
detailsHtml = Object.entries(concept.detailedExplanation).map(([key, section]) => `
🔍 ${section.title}
${section.explanation}
${section.pattern ? `
Pattern: ${section.pattern}
` : ''}
`).join('');
}
contentDiv.innerHTML = `
🔍 Detailed Explanation
${detailsHtml}
${concept.commonMistakes ? `
⚠️ Common Mistakes:
${concept.commonMistakes.map(mistake => `
❌ ${mistake.wrong}
✅ ${mistake.correct}
${mistake.explanation}
`).join('')}
` : ''}
`;
}
showComplexExamples() {
const contentDiv = document.getElementById('grammar-content');
contentDiv.innerHTML = `
🧩 Complex Examples
${this.complexExamples.map((example, index) => `
${example.chinese}
${example.english}
${example.pronunciation || ''}
${example.explanation || example.breakdown || ''}
`).join('')}
`;
}
showIntermediateExercises() {
if (this.intermediateExercises.length === 0) {
this.nextStep();
return;
}
this.currentExerciseSet = this.intermediateExercises;
this.currentExerciseIndex = 0;
this.showExercise('intermediate');
}
showSummary() {
const concept = this.conceptData;
const contentDiv = document.getElementById('grammar-content');
contentDiv.innerHTML = `
📝 Summary: ${concept.title}
🎯 What You've Learned:
Basic Concept:
${concept.explanation}
${concept.mainRules ? `
Key Rules:
${concept.mainRules.map(rule => `• ${rule}`).join('
')}
` : ''}
${concept.practicePoints ? `
Practice Points:
${concept.practicePoints.map(point => `• ${point}`).join('
')}
` : ''}
`;
}
showGlobalExercises() {
if (this.globalExercises.length === 0) {
this.completeRotation();
return;
}
this.currentExerciseSet = this.globalExercises;
this.currentExerciseIndex = 0;
this.showExercise('global');
}
// === UNIFIED EXERCISE SYSTEM ===
showExercise(type) {
const exercise = this.currentExerciseSet[this.currentExerciseIndex];
if (!exercise) {
this.nextStep();
return;
}
const contentDiv = document.getElementById('grammar-content');
const typeInfo = {
'basic': { title: '✏️ Basic Practice', color: '#48bb78' },
'intermediate': { title: '💪 Intermediate Practice', color: '#3182ce' },
'global': { title: '🏆 Final Test', color: '#805ad5' }
};
const info = typeInfo[type] || typeInfo['basic'];
if (exercise.type === 'correction') {
contentDiv.innerHTML = `
${info.title}
${exercise.question}
${exercise.options.map(option => `
`).join('')}
Explanation: ${exercise.explanation}
Exercise ${this.currentExerciseIndex + 1} of ${this.currentExerciseSet.length}
`;
} else {
// Fill in the blank
contentDiv.innerHTML = `
${info.title}
Fill in the blank: ${exercise.sentence}
${exercise.options.map(option => `
`).join('')}
Explanation: ${exercise.explanation}
Exercise ${this.currentExerciseIndex + 1} of ${this.currentExerciseSet.length}
`;
}
}
selectAnswer(selected, correct, exerciseType) {
const buttons = document.querySelectorAll('.option-btn');
const feedback = document.getElementById('feedback');
buttons.forEach(btn => {
btn.disabled = true;
if (btn.textContent.trim() === correct) {
btn.classList.add('correct');
} else if (btn.textContent.trim() === selected && selected !== correct) {
btn.classList.add('incorrect');
}
});
// Scoring based on exercise type
if (selected === correct) {
const points = exerciseType === 'global' ? 30 : (exerciseType === 'intermediate' ? 20 : 15);
this.score += points;
} else {
this.score = Math.max(0, this.score - 5);
}
this.onScoreUpdate(this.score);
document.getElementById('score-value').textContent = this.score;
feedback.classList.add('show');
setTimeout(() => {
this.currentExerciseIndex++;
if (this.currentExerciseIndex >= this.currentExerciseSet.length) {
// Finished this exercise set
this.nextStep();
} else {
// Show next exercise in set
this.showExercise(exerciseType);
}
}, 2500);
}
restart() {
this.score = 0;
this.currentStep = 0;
this.currentExerciseIndex = 0;
this.conceptSelected = false;
this.grammarConcept = null;
this.conceptData = {};
// Reset UI to concept selector
document.getElementById('score-value').textContent = '0';
document.getElementById('phase-icon').textContent = '🎯';
document.getElementById('phase-text').textContent = 'Select Grammar Concept';
document.getElementById('step-progress').style.display = 'none';
this.onScoreUpdate(0);
this.showConceptSelector();
}
// TTS Functions
speakChinese(text) {
if (window.SettingsManager && window.SettingsManager.speak) {
window.SettingsManager.speak(text, {
lang: 'zh-CN',
rate: 0.8
});
}
}
speakEnglish(text) {
if (window.SettingsManager && window.SettingsManager.speak) {
window.SettingsManager.speak(text, {
lang: 'en-US',
rate: 0.9
});
}
}
// Utility Functions
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;
}
restart() {
this.score = 0;
this.currentPhase = 'discovery';
this.currentRule = 0;
this.currentExampleIndex = 0;
this.currentPracticeIndex = 0;
this.practiceQuestions = this.shuffleArray(this.practiceQuestions);
document.getElementById('phase-text').innerHTML = `
🔍 Discovery Phase
`;
document.getElementById('score-value').textContent = '0';
this.onScoreUpdate(0);
this.startDiscovery();
}
start() {
// Game starts automatically in constructor
}
destroy() {
// Cleanup
const styleSheet = document.getElementById('grammar-discovery-styles');
if (styleSheet) {
styleSheet.remove();
}
if (window.currentGrammarGame === this) {
delete window.currentGrammarGame;
}
}
// === COMPATIBILITY SYSTEM ===
static getCompatibilityRequirements() {
return {
minimum: {
grammarRules: 1,
examples: 1
},
optimal: {
grammarRules: 2,
examples: 2,
exercises: 1
},
name: "Grammar Discovery",
description: "Grammar rule learning with examples and practice exercises"
};
}
static checkContentCompatibility(content) {
const requirements = GrammarDiscovery.getCompatibilityRequirements();
// Extract grammar data using same method as instance
const grammarData = GrammarDiscovery.extractGrammarDataStatic(content);
const ruleCount = grammarData.length;
const hasExamples = grammarData.some(rule => rule.examples && rule.examples.length > 0);
const hasExercises = grammarData.some(rule => rule.exercises && rule.exercises.length > 0);
const avgExamples = ruleCount > 0 ?
grammarData.reduce((sum, rule) => sum + (rule.examples ? rule.examples.length : 0), 0) / ruleCount : 0;
// Calculate score based on rules, examples, and exercises
// Dynamic percentage based on optimal volumes (1→2 rules, 1→2 examples, exercises bonus)
// Rules: 0=0%, 1=50%, 2=100%
// Examples: 0=0%, 1=50%, 2=100%
// Exercises: 0=0%, 1=100% (binary)
const ruleScore = Math.min(100, (ruleCount / requirements.optimal.grammarRules) * 100);
const exampleScore = Math.min(100, (avgExamples / requirements.optimal.examples) * 100);
const exerciseScore = hasExercises ? 100 : 0;
// Combined score (weighted: 50% rules, 30% examples, 20% exercises)
const finalScore = (ruleScore * 0.5) + (exampleScore * 0.3) + (exerciseScore * 0.2);
const recommendations = [];
if (ruleCount < requirements.optimal.grammarRules) {
recommendations.push(`Add ${requirements.optimal.grammarRules - ruleCount} more grammar rules`);
}
if (avgExamples < requirements.optimal.examples) {
recommendations.push("Add more examples for each grammar rule");
}
if (!hasExercises) {
recommendations.push("Add practice exercises for interactive learning");
}
return {
score: Math.round(finalScore),
details: {
grammarRules: {
found: ruleCount,
minimum: requirements.minimum.grammarRules,
optimal: requirements.optimal.grammarRules,
status: ruleCount >= requirements.minimum.grammarRules ? 'sufficient' : 'insufficient'
},
examples: {
average: Math.round(avgExamples * 10) / 10,
minimum: requirements.minimum.examples,
optimal: requirements.optimal.examples,
status: avgExamples >= requirements.minimum.examples ? 'sufficient' : 'insufficient'
},
exercises: {
available: hasExercises ? 'yes' : 'no',
status: hasExercises ? 'available' : 'missing'
}
},
recommendations: recommendations
};
}
static extractGrammarDataStatic(content) {
let grammarRules = [];
// Priority 1: Use raw module content
if (content.rawContent) {
// Extract from grammar object
if (content.rawContent.grammar && typeof content.rawContent.grammar === 'object') {
grammarRules = Object.entries(content.rawContent.grammar).map(([ruleKey, ruleData]) => {
if (typeof ruleData === 'object') {
return {
key: ruleKey,
title: ruleData.title || ruleKey,
explanation: ruleData.explanation || '',
examples: ruleData.examples || [],
exercises: ruleData.exercises || [],
category: ruleData.category || 'general'
};
}
return null;
}).filter(Boolean);
}
}
// Priority 2: Direct content properties
if (content.grammar && typeof content.grammar === 'object') {
grammarRules = Object.entries(content.grammar).map(([ruleKey, ruleData]) => {
if (typeof ruleData === 'object') {
return {
key: ruleKey,
title: ruleData.title || ruleKey,
explanation: ruleData.explanation || '',
examples: ruleData.examples || [],
exercises: ruleData.exercises || [],
category: ruleData.category || 'general'
};
}
return null;
}).filter(Boolean);
}
// Filter and validate grammar rules
grammarRules = grammarRules.filter(rule =>
rule &&
typeof rule.title === 'string' &&
rule.title.trim() !== '' &&
typeof rule.explanation === 'string' &&
rule.explanation.trim() !== ''
);
return grammarRules;
}
}
// Export to global
window.GameModules = window.GameModules || {};
window.GameModules.GrammarDiscovery = GrammarDiscovery;