Class_generator/Legacy/js/games/grammar-discovery.js
StillHammer 38920cc858 Complete architectural rewrite with ultra-modular system
Major Changes:
- Moved legacy system to Legacy/ folder for archival
- Built new modular architecture with strict separation of concerns
- Created core system: Module, EventBus, ModuleLoader, Router
- Added Application bootstrap with auto-start functionality
- Implemented development server with ES6 modules support
- Created comprehensive documentation and project context
- Converted SBS-7-8 content to JSON format
- Copied all legacy games and content to new structure

New Architecture Features:
- Sealed modules with WeakMap private data
- Strict dependency injection system
- Event-driven communication only
- Inviolable responsibility patterns
- Auto-initialization without commands
- Component-based UI foundation ready

Technical Stack:
- Vanilla JS/HTML/CSS only
- ES6 modules with proper imports/exports
- HTTP development server (no file:// protocol)
- Modular CSS with component scoping
- Comprehensive error handling and debugging

Ready for Phase 2: Converting legacy modules to new architecture

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-22 07:08:39 +08:00

1185 lines
41 KiB
JavaScript

// === 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 = `
<div class="grammar-discovery">
<div class="grammar-hud">
<div class="grammar-phase">
<span class="phase-icon" id="phase-icon">🎯</span>
<span id="phase-text">Select Grammar Concept</span>
</div>
<div class="step-progress" id="step-progress" style="display: none;">
Step <span id="current-step">1</span> of ${this.rotationSteps.length}
</div>
<div class="score">Score: <span id="score-value">0</span></div>
</div>
<div class="grammar-content" id="grammar-content">
<!-- Content will be populated dynamically -->
</div>
</div>
`;
this.showConceptSelector();
}
showConceptSelector() {
if (this.availableConcepts.length === 0) {
console.error('No concepts available for selection');
return;
}
const contentDiv = document.getElementById('grammar-content');
contentDiv.innerHTML = `
<div class="concept-selector">
<div class="selector-title">
🎯 Choose Grammar Concept
</div>
<div class="selector-description">
Select which grammar concept you want to focus on for intensive study with our 8-step rotation system.
</div>
<select class="concept-dropdown" id="concept-dropdown" onchange="window.currentGrammarGame.previewConcept(this.value)">
<option value="">-- Select a Grammar Concept --</option>
${this.availableConcepts.map(concept => `
<option value="${concept.id}">${concept.title}</option>
`).join('')}
</select>
<div class="concept-preview" id="concept-preview">
<!-- Preview content will be shown here -->
</div>
<button class="discover-btn" id="start-btn" onclick="window.currentGrammarGame.startSelectedConcept()" disabled>
🚀 Start Learning This Concept
</button>
</div>
`;
// 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 = `
<div class="preview-title">${concept.title}</div>
<div class="preview-explanation">${concept.explanation}</div>
<div class="preview-stats">
<div class="stat-item">
<strong>${this.simpleExamples.length}</strong><br>
Simple Examples
</div>
<div class="stat-item">
<strong>${this.complexExamples.length}</strong><br>
Complex Examples
</div>
<div class="stat-item">
<strong>${this.basicExercises.length}</strong><br>
Basic Exercises
</div>
<div class="stat-item">
<strong>${this.intermediateExercises.length}</strong><br>
Intermediate Exercises
</div>
<div class="stat-item">
<strong>${this.globalExercises.length}</strong><br>
Final Test Questions
</div>
</div>
`;
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 = `
<div class="phase-complete">
<div class="complete-icon">🎉</div>
<div class="complete-title">Grammar Concept Mastered!</div>
<p>You've completed the full rotation for: <strong>${this.conceptData.title}</strong></p>
<p>Final Score: <strong>${this.score}</strong></p>
<button class="discover-btn" onclick="window.currentGrammarGame.restart()">
🔄 Learn Again
</button>
</div>
`;
this.onGameEnd(this.score);
}
// === ROTATION STEP IMPLEMENTATIONS ===
showBasicExplanation() {
const concept = this.conceptData;
const contentDiv = document.getElementById('grammar-content');
contentDiv.innerHTML = `
<div class="rule-card active">
<div class="rule-title">
📚 ${concept.title}
</div>
<div class="rule-explanation">
${concept.explanation}
</div>
${concept.mainRules ? `
<div class="rules-list">
<h4>🎯 Key Rules:</h4>
<ul>
${concept.mainRules.slice(0, 3).map(rule => `<li>${rule}</li>`).join('')}
</ul>
</div>
` : ''}
<div class="discovery-controls">
<button class="discover-btn" onclick="window.currentGrammarGame.nextStep()">
➡️ Continue to Examples
</button>
</div>
</div>
`;
}
showSimpleExamples() {
const contentDiv = document.getElementById('grammar-content');
contentDiv.innerHTML = `
<div class="rule-card active">
<div class="rule-title">
💡 Simple Examples
</div>
<div class="examples-section">
${this.simpleExamples.map((example, index) => `
<div class="example-item revealed" id="simple-example-${index}">
<div class="chinese-text">${example.chinese}</div>
<div class="english-text">${example.english}</div>
<div class="pronunciation">${example.pronunciation || ''}</div>
<div class="explanation-text">${example.explanation || example.breakdown || ''}</div>
<div class="tts-controls">
<button class="tts-btn" onclick="window.currentGrammarGame.speakChinese('${example.chinese}')">
🔊 Chinese
</button>
<button class="tts-btn" onclick="window.currentGrammarGame.speakEnglish('${example.english}')">
🔊 English
</button>
</div>
</div>
`).join('')}
</div>
<div class="discovery-controls">
<button class="discover-btn" onclick="window.currentGrammarGame.nextStep()">
➡️ Practice Basic Exercises
</button>
</div>
</div>
`;
// 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]) => `
<div class="detail-section">
<h4>🔍 ${section.title}</h4>
<p>${section.explanation}</p>
${section.pattern ? `<div class="pattern">Pattern: <code>${section.pattern}</code></div>` : ''}
</div>
`).join('');
}
contentDiv.innerHTML = `
<div class="rule-card active">
<div class="rule-title">
🔍 Detailed Explanation
</div>
${detailsHtml}
${concept.commonMistakes ? `
<div class="mistakes-section">
<h4>⚠️ Common Mistakes:</h4>
${concept.commonMistakes.map(mistake => `
<div class="mistake-item">
<div class="mistake-wrong">❌ ${mistake.wrong}</div>
<div class="mistake-correct">✅ ${mistake.correct}</div>
<div class="mistake-explanation">${mistake.explanation}</div>
</div>
`).join('')}
</div>
` : ''}
<div class="discovery-controls">
<button class="discover-btn" onclick="window.currentGrammarGame.nextStep()">
➡️ See Complex Examples
</button>
</div>
</div>
`;
}
showComplexExamples() {
const contentDiv = document.getElementById('grammar-content');
contentDiv.innerHTML = `
<div class="rule-card active">
<div class="rule-title">
🧩 Complex Examples
</div>
<div class="examples-section">
${this.complexExamples.map((example, index) => `
<div class="example-item revealed" id="complex-example-${index}">
<div class="chinese-text">${example.chinese}</div>
<div class="english-text">${example.english}</div>
<div class="pronunciation">${example.pronunciation || ''}</div>
<div class="explanation-text">${example.explanation || example.breakdown || ''}</div>
<div class="tts-controls">
<button class="tts-btn" onclick="window.currentGrammarGame.speakChinese('${example.chinese}')">
🔊 Chinese
</button>
<button class="tts-btn" onclick="window.currentGrammarGame.speakEnglish('${example.english}')">
🔊 English
</button>
</div>
</div>
`).join('')}
</div>
<div class="discovery-controls">
<button class="discover-btn" onclick="window.currentGrammarGame.nextStep()">
➡️ Intermediate Practice
</button>
</div>
</div>
`;
}
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 = `
<div class="rule-card active">
<div class="rule-title">
📝 Summary: ${concept.title}
</div>
<div class="summary-section">
<h4>🎯 What You've Learned:</h4>
<div class="summary-grid">
<div class="summary-item">
<strong>Basic Concept:</strong><br>
${concept.explanation}
</div>
${concept.mainRules ? `
<div class="summary-item">
<strong>Key Rules:</strong><br>
${concept.mainRules.map(rule => `${rule}`).join('<br>')}
</div>
` : ''}
${concept.practicePoints ? `
<div class="summary-item">
<strong>Practice Points:</strong><br>
${concept.practicePoints.map(point => `${point}`).join('<br>')}
</div>
` : ''}
</div>
</div>
<div class="discovery-controls">
<button class="discover-btn" onclick="window.currentGrammarGame.nextStep()">
🏆 Take Final Test
</button>
</div>
</div>
`;
}
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 = `
<div class="practice-question">
<div class="rule-title" style="color: ${info.color}">
${info.title}
</div>
<div class="question-text">
${exercise.question}
</div>
<div class="options-grid">
${exercise.options.map(option => `
<button class="option-btn" onclick="window.currentGrammarGame.selectAnswer('${option.replace(/'/g, "\\'")}', '${exercise.correctAnswer.replace(/'/g, "\\'")}', '${type}')">
${option}
</button>
`).join('')}
</div>
<div class="feedback" id="feedback">
<strong>Explanation:</strong> ${exercise.explanation}
</div>
<div class="exercise-progress">
Exercise ${this.currentExerciseIndex + 1} of ${this.currentExerciseSet.length}
</div>
</div>
`;
} else {
// Fill in the blank
contentDiv.innerHTML = `
<div class="practice-question">
<div class="rule-title" style="color: ${info.color}">
${info.title}
</div>
<div class="question-text">
Fill in the blank: <strong>${exercise.sentence}</strong>
</div>
<div class="options-grid">
${exercise.options.map(option => `
<button class="option-btn" onclick="window.currentGrammarGame.selectAnswer('${option}', '${exercise.correctAnswer}', '${type}')">
${option}
</button>
`).join('')}
</div>
<div class="feedback" id="feedback">
<strong>Explanation:</strong> ${exercise.explanation}
</div>
<div class="exercise-progress">
Exercise ${this.currentExerciseIndex + 1} of ${this.currentExerciseSet.length}
</div>
</div>
`;
}
}
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 = `
<span class="phase-icon">🔍</span> 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;
}
}
}
// Export to global
window.GameModules = window.GameModules || {};
window.GameModules.GrammarDiscovery = GrammarDiscovery;