diff --git a/js/core/content-game-compatibility.js b/js/core/content-game-compatibility.js
index 7888e52..0cadddc 100644
--- a/js/core/content-game-compatibility.js
+++ b/js/core/content-game-compatibility.js
@@ -9,7 +9,6 @@ class ContentGameCompatibility {
'memory-match': 50,
'quiz-game': 30,
'fill-the-blank': 30,
- 'text-reader': 40,
'adventure-reader': 50,
'chinese-study': 35,
'story-builder': 35,
@@ -119,7 +118,6 @@ class ContentGameCompatibility {
case 'fill-the-blank':
return this.calculateFillBlankCompat(capabilities);
- case 'text-reader':
case 'story-reader':
return this.calculateTextReaderCompat(capabilities);
@@ -467,7 +465,6 @@ class ContentGameCompatibility {
'memory-match': ['4+ paires de vocabulaire', 'Idéalement avec images/audio'],
'quiz-game': ['Vocabulaire OU phrases OU exercices', 'Très flexible'],
'fill-the-blank': ['Phrases avec exercices à trous OU phrases simples', 'Contenu éducatif'],
- 'text-reader': ['3+ phrases OU dialogues', 'Contenu narratif'],
'adventure-reader': ['Dialogues + contenu narratif riche', 'Histoire cohérente'],
'chinese-study': ['Vocabulaire et phrases chinoises', 'Audio recommandé'],
'story-builder': ['Dialogues OU 5+ phrases', 'Vocabulaire varié'],
@@ -484,7 +481,6 @@ class ContentGameCompatibility {
'memory-match': 'Jeu de mémoire optimisé pour paires vocabulaire-traduction',
'quiz-game': 'Jeu polyvalent compatible avec la plupart des contenus',
'fill-the-blank': 'Exercices à trous nécessitant phrases structurées',
- 'text-reader': 'Lecture guidée nécessitant textes ou dialogues',
'adventure-reader': 'Aventure narrative nécessitant contenu riche',
'chinese-study': 'Optimisé pour apprentissage du chinois',
'story-builder': 'Construction narrative nécessitant éléments variés',
diff --git a/js/core/content-scanner.js b/js/core/content-scanner.js
index d58bffa..da7f2de 100644
--- a/js/core/content-scanner.js
+++ b/js/core/content-scanner.js
@@ -875,7 +875,6 @@ class ContentScanner {
'memory-match': this.calculateMemoryMatchCompat(capabilities),
'quiz-game': this.calculateQuizGameCompat(capabilities),
'fill-the-blank': this.calculateFillBlankCompat(capabilities),
- 'text-reader': this.calculateTextReaderCompat(capabilities),
'adventure-reader': this.calculateAdventureCompat(capabilities),
'sentence-builder': this.calculateSentenceBuilderCompat(capabilities),
'pronunciation-game': this.calculatePronunciationCompat(capabilities),
@@ -986,14 +985,6 @@ class ContentScanner {
return { compatible: score >= 30, score, reason: 'Nécessite phrases à trous' };
}
- calculateTextReaderCompat(capabilities) {
- let score = 0;
- if (capabilities.hasSentences) score += 40;
- if (capabilities.hasDialogues) score += 50;
- if (capabilities.hasAudioFiles) score += 10;
-
- return { compatible: score >= 40, score, reason: 'Nécessite textes à lire' };
- }
calculateAdventureCompat(capabilities) {
let score = 0;
diff --git a/js/core/game-loader.js b/js/core/game-loader.js
index 7ca5729..f400af5 100644
--- a/js/core/game-loader.js
+++ b/js/core/game-loader.js
@@ -318,7 +318,6 @@ const GameLoader = {
'memory-match': 'MemoryMatch',
'quiz-game': 'QuizGame',
'fill-the-blank': 'FillTheBlank',
- 'text-reader': 'TextReader',
'adventure-reader': 'AdventureReader',
'chinese-study': 'ChineseStudy',
'story-reader': 'StoryReader',
@@ -364,7 +363,6 @@ const GameLoader = {
'memory-match': 'Memory Match',
'quiz-game': 'Quiz Game',
'fill-the-blank': 'Fill the Blank',
- 'text-reader': 'Text Reader',
'adventure-reader': 'Adventure Reader'
};
diff --git a/js/core/navigation.js b/js/core/navigation.js
index 980e94d..4542774 100644
--- a/js/core/navigation.js
+++ b/js/core/navigation.js
@@ -74,12 +74,6 @@ const AppNavigation = {
icon: '📝',
description: 'Complete sentences by filling in the blanks!'
},
- 'text-reader': {
- enabled: true,
- name: 'Text Reader',
- icon: '📖',
- description: 'Read texts sentence by sentence'
- },
'adventure-reader': {
enabled: true,
name: 'Adventure Reader',
diff --git a/js/games/adventure-reader.js b/js/games/adventure-reader.js
index 698b570..6e81dca 100644
--- a/js/games/adventure-reader.js
+++ b/js/games/adventure-reader.js
@@ -74,7 +74,7 @@ class AdventureReaderGame {
📖 sentences: Individual phrases for reading practice
Add adventure content to enable this game mode.
-
+
`;
}
diff --git a/js/games/chinese-study.js b/js/games/chinese-study.js
index 3cbcbfe..5f13f83 100644
--- a/js/games/chinese-study.js
+++ b/js/games/chinese-study.js
@@ -39,7 +39,7 @@ class ChineseStudyGame {
❌ Error loading
This content doesn't have Chinese vocabulary for the Chinese Study Game.
The game needs vocabulary with Chinese characters, translations, and optional pinyin.
-
+
`;
this.addStyles();
@@ -214,7 +214,7 @@ class ChineseStudyGame {
-
+
`;
@@ -643,7 +643,7 @@ class ChineseStudyGame {
-
+
diff --git a/js/games/fill-the-blank.js b/js/games/fill-the-blank.js
index a2dad20..9377c02 100644
--- a/js/games/fill-the-blank.js
+++ b/js/games/fill-the-blank.js
@@ -42,7 +42,7 @@ class FillTheBlankGame {
❌ Loading Error
This content does not contain vocabulary compatible with Fill the Blank.
The game requires words with their translations in ultra-modular format.
-
+
`;
}
diff --git a/js/games/memory-match.js b/js/games/memory-match.js
index 0166e17..d3ceb40 100644
--- a/js/games/memory-match.js
+++ b/js/games/memory-match.js
@@ -41,7 +41,7 @@ class MemoryMatchGame {
❌ Error loading
This content doesn't have enough vocabulary for Memory Match.
The game needs at least ${this.totalPairs} vocabulary pairs.
-
+
`;
}
diff --git a/js/games/quiz-game.js b/js/games/quiz-game.js
index ed70522..7d59be5 100644
--- a/js/games/quiz-game.js
+++ b/js/games/quiz-game.js
@@ -15,16 +15,18 @@ class QuizGame {
this.correctAnswers = 0;
this.currentQuestionData = null;
this.hasAnswered = false;
-
- // Extract vocabulary
+ 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 < 4) {
+ if (!this.vocabulary || this.vocabulary.length < 6) {
logSh('Not enough vocabulary for Quiz Game', 'ERROR');
this.showInitError();
return;
@@ -42,8 +44,8 @@ class QuizGame {
❌ Error loading
This content doesn't have enough vocabulary for Quiz Game.
-
The game needs at least 4 vocabulary items.
-
+
The game needs at least 6 vocabulary items.
+
`;
}
@@ -171,6 +173,76 @@ class QuizGame {
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--) {
@@ -268,26 +340,43 @@ class QuizGame {
}
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
+
+ // 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, 3)
- .map(item => item.translation);
+ .slice(0, 5)
+ .map(item => sourceForWrongAnswers === 'translation' ? item.translation : item.original);
- // Combine and shuffle all options
- const allOptions = [correctAnswer.translation, ...wrongAnswers].sort(() => Math.random() - 0.5);
+ // Combine and shuffle all options (1 correct + 5 wrong = 6 total)
+ const allOptions = [correctAnswerText, ...wrongAnswers].sort(() => Math.random() - 0.5);
this.currentQuestionData = {
- question: correctAnswer.original,
- correctAnswer: correctAnswer.translation,
- options: allOptions
+ question: questionText,
+ correctAnswer: correctAnswerText,
+ options: allOptions,
+ direction: this.quizDirection
};
-
+
this.renderQuestion();
this.updateProgress();
}
@@ -295,9 +384,13 @@ class QuizGame {
renderQuestion() {
const { question, options } = this.currentQuestionData;
- // Update question text
+ // 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 = `
- What is the translation of "${question}"?
+ ${directionText} "${question}"?
`;
// Clear and generate options
diff --git a/js/games/story-builder.js b/js/games/story-builder.js
index 9d55312..49eef8a 100644
--- a/js/games/story-builder.js
+++ b/js/games/story-builder.js
@@ -50,7 +50,7 @@ class StoryBuilderGame {
❌ Error loading
This content doesn't have enough vocabulary for Story Builder.
The game needs at least 6 vocabulary words with types (noun, verb, adjective, etc.).
-
+
`;
}
diff --git a/js/games/story-reader.js b/js/games/story-reader.js
index 2c046a8..2446b55 100644
--- a/js/games/story-reader.js
+++ b/js/games/story-reader.js
@@ -109,6 +109,34 @@ class StoryReader {
});
}
+ // NEW: Check for simple texts and convert them to stories
+ const texts = this.content.rawContent?.texts || this.content.texts;
+ if (texts && Array.isArray(texts)) {
+ texts.forEach((text, index) => {
+ if (text && (text.title || text.original_language)) {
+ const convertedStory = this.convertTextToStory(text, index);
+ this.availableStories.push({
+ id: `text_${index}`,
+ title: text.title || `Text ${index + 1}`,
+ data: convertedStory,
+ source: 'text'
+ });
+ }
+ });
+ }
+
+ // NEW: Check for sentences and create a story from them
+ const sentences = this.content.rawContent?.sentences || this.content.sentences;
+ if (sentences && Array.isArray(sentences) && sentences.length > 0 && this.availableStories.length === 0) {
+ const sentencesStory = this.convertSentencesToStory(sentences);
+ this.availableStories.push({
+ id: 'sentences',
+ title: 'Reading Practice',
+ data: sentencesStory,
+ source: 'sentences'
+ });
+ }
+
logSh(`📚 Discovered ${this.availableStories.length} stories:`, this.availableStories.map(s => s.title), 'INFO');
}
@@ -141,7 +169,7 @@ class StoryReader {
❌ Error
${message}
-
+
`;
}
@@ -1018,6 +1046,104 @@ class StoryReader {
}
}
+ // NEW: Convert simple text to story format
+ convertTextToStory(text, index) {
+ // Split text into sentences for easier reading
+ const sentences = this.splitTextIntoSentences(text.original_language, text.user_language);
+
+ return {
+ title: text.title || `Text ${index + 1}`,
+ totalSentences: sentences.length,
+ chapters: [{
+ title: "Reading Text",
+ sentences: sentences
+ }]
+ };
+ }
+
+ // NEW: Convert array of sentences to story format
+ convertSentencesToStory(sentences) {
+ const storyTitle = this.content.name || "Reading Practice";
+
+ const convertedSentences = sentences.map((sentence, index) => ({
+ id: index + 1,
+ original: sentence.original_language || sentence.english || sentence.original || '',
+ translation: sentence.user_language || sentence.chinese || sentence.french || sentence.translation || '',
+ words: this.breakSentenceIntoWords(
+ sentence.original_language || sentence.english || sentence.original || '',
+ sentence.user_language || sentence.chinese || sentence.french || sentence.translation || ''
+ )
+ }));
+
+ return {
+ title: storyTitle,
+ totalSentences: convertedSentences.length,
+ chapters: [{
+ title: "Reading Sentences",
+ sentences: convertedSentences
+ }]
+ };
+ }
+
+ // NEW: Split long text into manageable sentences
+ splitTextIntoSentences(originalText, translationText) {
+ // Split by sentence endings
+ const originalSentences = originalText.split(/[.!?]+/).filter(s => s.trim().length > 0);
+ const translationSentences = translationText.split(/[.!?]+/).filter(s => s.trim().length > 0);
+
+ const sentences = [];
+ const maxSentences = Math.max(originalSentences.length, translationSentences.length);
+
+ for (let i = 0; i < maxSentences; i++) {
+ const original = (originalSentences[i] || '').trim();
+ const translation = (translationSentences[i] || '').trim();
+
+ if (original || translation) {
+ sentences.push({
+ id: i + 1,
+ original: original + (original && !original.match(/[.!?]$/) ? '.' : ''),
+ translation: translation + (translation && !translation.match(/[.!?]$/) ? '.' : ''),
+ words: this.breakSentenceIntoWords(original, translation)
+ });
+ }
+ }
+
+ return sentences;
+ }
+
+ // NEW: Break sentence into word-by-word format for Story Reader
+ breakSentenceIntoWords(original, translation) {
+ if (!original) return [];
+
+ const words = original.split(/\s+/).filter(word => word.trim().length > 0);
+ const translationWords = translation ? translation.split(/\s+/).filter(word => word.trim().length > 0) : [];
+
+ return words.map((word, index) => {
+ // Clean punctuation for word lookup
+ const cleanWord = word.replace(/[.,!?;:"'()[\]{}\-–—]/g, '').toLowerCase();
+
+ // Try to find in vocabulary
+ let wordTranslation = translationWords[index] || '';
+ let wordType = 'word';
+ let pronunciation = '';
+
+ // Look up in content vocabulary if available
+ if (this.vocabulary && this.vocabulary[cleanWord]) {
+ const vocabEntry = this.vocabulary[cleanWord];
+ wordTranslation = vocabEntry.user_language || vocabEntry.translation || wordTranslation;
+ wordType = vocabEntry.type || 'word';
+ pronunciation = vocabEntry.pronunciation || '';
+ }
+
+ return {
+ word: word,
+ translation: wordTranslation,
+ type: wordType,
+ pronunciation: pronunciation
+ };
+ });
+ }
+
// TTS Methods
playSentenceTTS() {
const sentenceData = this.getCurrentSentenceData();
@@ -1163,7 +1289,7 @@ class StoryReader {
Words read: ${this.wordsRead}
Total sentences: ${this.totalSentences}
-
+
`;
diff --git a/js/games/text-reader.js b/js/games/text-reader.js
deleted file mode 100644
index faff052..0000000
--- a/js/games/text-reader.js
+++ /dev/null
@@ -1,535 +0,0 @@
-// === MODULE TEXT READER ===
-
-class TextReaderGame {
- constructor(options) {
- this.container = options.container;
- this.content = options.content;
- this.onScoreUpdate = options.onScoreUpdate || (() => {});
- this.onGameEnd = options.onGameEnd || (() => {});
-
- // Reader state
- this.currentTextIndex = 0;
- this.currentSentenceIndex = 0;
- this.isRunning = false;
-
- // Reading data
- this.texts = this.extractTexts(this.content);
- this.sentencesFromContent = this.extractSentences(this.content);
- this.vocabulary = this.extractVocabulary(this.content);
- this.currentText = null;
- this.sentences = [];
- this.showingFullText = false;
-
- this.init();
- }
-
- init() {
- // Check that we have texts, sentences, or vocabulary
- if ((!this.texts || this.texts.length === 0) &&
- (!this.sentencesFromContent || this.sentencesFromContent.length === 0) &&
- (!this.vocabulary || this.vocabulary.length === 0)) {
- logSh('No texts, sentences, or vocabulary available for Text Reader', 'ERROR');
- this.showInitError();
- return;
- }
-
- this.createReaderInterface();
- this.setupEventListeners();
- this.loadText();
- }
-
- showInitError() {
- this.container.innerHTML = `
-
-
❌ Error loading
-
This content doesn't contain texts, sentences, or vocabulary compatible with Text Reader.
-
The reader needs content with ultra-modular format (original_language/user_language).
-
-
- `;
- }
-
- extractTexts(content) {
- let texts = [];
-
- logSh('📖 Extracting texts from:', content?.name || 'content', 'INFO');
-
- // Priority 1: Use raw module content (simple format)
- if (content.rawContent) {
- logSh('📦 Using raw module content', 'INFO');
- return this.extractTextsFromRaw(content.rawContent);
- }
-
- // Priority 2: Ultra-modular format (texts array) - ONLY format supported
- if (content.texts && Array.isArray(content.texts)) {
- logSh('✨ Ultra-modular format detected (texts array)', 'INFO');
- texts = content.texts
- .filter(text => text && text.original_language && text.user_language)
- .map(text => ({
- id: text.id || `text_${Date.now()}_${Math.random()}`,
- title: text.title || text.id || 'Text',
- original: text.original_language,
- translation: text.user_language
- }));
- logSh(`✨ ${texts.length} texts extracted from ultra-modular format`, 'INFO');
- }
-
- return this.finalizeTexts(texts);
- }
-
- extractTextsFromRaw(rawContent) {
- logSh('🔧 Extracting from raw content:', rawContent.name || 'Module', 'INFO');
- let texts = [];
-
- // Ultra-modular format (texts array) - ONLY format supported
- if (rawContent.texts && Array.isArray(rawContent.texts)) {
- texts = rawContent.texts
- .filter(text => text && text.original_language && text.user_language)
- .map(text => ({
- id: text.id || `text_${Date.now()}_${Math.random()}`,
- title: text.title || text.id || 'Text',
- original: text.original_language,
- translation: text.user_language
- }));
- logSh(`✨ ${texts.length} texts extracted from ultra-modular format`, 'INFO');
- }
-
- return this.finalizeTexts(texts);
- }
-
- finalizeTexts(texts) {
- // Validation and cleanup
- texts = texts.filter(text =>
- text &&
- typeof text.original === 'string' &&
- text.original.trim() !== '' &&
- typeof text.translation === 'string' &&
- text.translation.trim() !== ''
- );
-
- if (texts.length === 0) {
- logSh('❌ No valid texts found', 'ERROR');
- // Demo texts as fallback
- texts = [
- {
- id: "demo_text_1",
- title: "Demo Text",
- original: "This is a demo text. It has multiple sentences. Each sentence will be displayed one by one. You can navigate using the buttons below.",
- translation: "Ceci est un texte de démonstration. Il contient plusieurs phrases. Chaque phrase sera affichée une par une. Vous pouvez naviguer avec les boutons ci-dessous."
- }
- ];
- logSh('🚨 Using demo texts', 'WARN');
- }
-
- logSh(`✅ Text Reader: ${texts.length} texts finalized`, 'INFO');
- return texts;
- }
-
- extractSentences(content) {
- let sentences = [];
-
- logSh('📝 Extracting sentences from:', content?.name || 'content', 'INFO');
-
- // Priority 1: Use raw module content
- if (content.rawContent) {
- logSh('📦 Using raw module content for sentences', 'INFO');
- return this.extractSentencesFromRaw(content.rawContent);
- }
-
- // Priority 2: Ultra-modular format (sentences array) - ONLY format supported
- if (content.sentences && Array.isArray(content.sentences)) {
- logSh('✨ Ultra-modular format detected (sentences array)', 'INFO');
- sentences = content.sentences
- .filter(sentence => sentence && sentence.original_language && sentence.user_language)
- .map(sentence => ({
- id: sentence.id || `sentence_${Date.now()}_${Math.random()}`,
- original: sentence.original_language,
- translation: sentence.user_language
- }));
- logSh(`✨ ${sentences.length} sentences extracted from ultra-modular format`, 'INFO');
- }
-
- return this.finalizeSentences(sentences);
- }
-
- extractSentencesFromRaw(rawContent) {
- logSh('🔧 Extracting sentences from raw content:', rawContent.name || 'Module', 'INFO');
- let sentences = [];
-
- // Ultra-modular format (sentences array) - ONLY format supported
- if (rawContent.sentences && Array.isArray(rawContent.sentences)) {
- sentences = rawContent.sentences
- .filter(sentence => sentence && sentence.original_language && sentence.user_language)
- .map(sentence => ({
- id: sentence.id || `sentence_${Date.now()}_${Math.random()}`,
- original: sentence.original_language,
- translation: sentence.user_language
- }));
- logSh(`✨ ${sentences.length} sentences extracted from ultra-modular format`, 'INFO');
- }
-
- return this.finalizeSentences(sentences);
- }
-
- finalizeSentences(sentences) {
- // Validation and cleanup
- sentences = sentences.filter(sentence =>
- sentence &&
- typeof sentence.original === 'string' &&
- sentence.original.trim() !== '' &&
- typeof sentence.translation === 'string' &&
- sentence.translation.trim() !== ''
- );
-
- logSh(`✅ Text Reader: ${sentences.length} sentences finalized`, 'INFO');
- return sentences;
- }
-
- 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 for vocabulary', '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,
- type: data.type || 'unknown'
- };
- }
- return null;
- }).filter(item => item !== null);
- logSh(`✨ ${vocabulary.length} vocabulary words extracted from ultra-modular format`, 'INFO');
- }
-
- return this.finalizeVocabulary(vocabulary);
- }
-
- extractVocabularyFromRaw(rawContent) {
- logSh('🔧 Extracting vocabulary 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,
- type: data.type || 'unknown'
- };
- }
- return null;
- }).filter(item => item !== null);
- logSh(`✨ ${vocabulary.length} vocabulary words extracted from ultra-modular format`, 'INFO');
- }
-
- return this.finalizeVocabulary(vocabulary);
- }
-
- finalizeVocabulary(vocabulary) {
- // Validation and cleanup
- vocabulary = vocabulary.filter(word =>
- word &&
- typeof word.original === 'string' &&
- word.original.trim() !== '' &&
- typeof word.translation === 'string' &&
- word.translation.trim() !== ''
- );
-
- logSh(`✅ Text Reader: ${vocabulary.length} vocabulary words finalized`, 'INFO');
- return vocabulary;
- }
-
- createReaderInterface() {
- this.container.innerHTML = `
-
-
-
-
-
-
- 1 / 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Use Next/Previous buttons to navigate through sentences
-
-
-
- `;
- }
-
- setupEventListeners() {
- document.getElementById('next-sentence-btn').addEventListener('click', () => this.nextSentence());
- document.getElementById('prev-sentence-btn').addEventListener('click', () => this.prevSentence());
- document.getElementById('show-full-btn').addEventListener('click', () => this.showFullText());
-
- document.getElementById('back-to-reading-btn').addEventListener('click', () => this.backToReading());
- document.getElementById('text-selector').addEventListener('change', (e) => this.selectText(parseInt(e.target.value)));
-
- // Keyboard navigation
- document.addEventListener('keydown', (e) => {
- if (!this.isRunning) return;
-
- if (this.showingFullText) {
- if (e.key === 'Escape') this.backToReading();
- } else {
- if (e.key === 'ArrowLeft') this.prevSentence();
- else if (e.key === 'ArrowRight') this.nextSentence();
- else if (e.key === 'Enter' || e.key === ' ') this.showFullText();
- }
- });
- }
-
- start() {
- logSh('📖 Text Reader: Starting', 'INFO');
- this.isRunning = true;
- }
-
- restart() {
- logSh('🔄 Text Reader: Restarting', 'INFO');
- this.reset();
- this.start();
- }
-
- reset() {
- this.currentTextIndex = 0;
- this.currentSentenceIndex = 0;
- this.isRunning = false;
- this.showingFullText = false;
- this.loadText();
- }
-
- loadText() {
- // Use texts if available, otherwise use individual sentences, then vocabulary
- if (this.texts && this.texts.length > 0) {
- if (this.currentTextIndex >= this.texts.length) {
- this.currentTextIndex = 0;
- }
-
- this.currentText = this.texts[this.currentTextIndex];
- this.sentences = this.splitIntoSentences(this.currentText.original);
- this.currentSentenceIndex = 0;
- this.showingFullText = false;
-
- this.populateTextSelector();
- } else if (this.sentencesFromContent && this.sentencesFromContent.length > 0) {
- // Use individual sentences as "mini-texts"
- this.texts = this.sentencesFromContent.map((sentence, index) => ({
- id: sentence.id || `sentence_text_${index}`,
- title: `Sentence ${index + 1}`,
- original: sentence.original,
- translation: sentence.translation
- }));
-
- this.currentText = this.texts[0];
- this.sentences = [this.currentText.original]; // Single sentence
- this.currentSentenceIndex = 0;
- this.showingFullText = false;
-
- this.populateTextSelector();
- } else if (this.vocabulary && this.vocabulary.length > 0) {
- // Use vocabulary words as "mini-texts"
- this.texts = this.vocabulary.map((word, index) => ({
- id: `vocab_text_${index}`,
- title: `Word: ${word.original}`,
- original: word.original,
- translation: word.translation
- }));
-
- this.currentText = this.texts[0];
- this.sentences = [this.currentText.original]; // Single word
- this.currentSentenceIndex = 0;
- this.showingFullText = false;
-
- this.populateTextSelector();
- }
-
- this.updateDisplay();
- this.updateUI();
- }
-
- populateTextSelector() {
- const selector = document.getElementById('text-selector');
- selector.innerHTML = '';
-
- this.texts.forEach((text, index) => {
- const option = document.createElement('option');
- option.value = index;
- option.textContent = text.title || `Text ${index + 1}`;
- if (index === this.currentTextIndex) {
- option.selected = true;
- }
- selector.appendChild(option);
- });
- }
-
- selectText(textIndex) {
- if (textIndex >= 0 && textIndex < this.texts.length) {
- this.currentTextIndex = textIndex;
- this.currentText = this.texts[this.currentTextIndex];
- this.sentences = this.splitIntoSentences(this.currentText.original);
- this.currentSentenceIndex = 0;
-
- // Always go back to sentence reading when changing text
- if (this.showingFullText) {
- this.backToReading();
- } else {
- this.updateDisplay();
- this.updateUI();
- }
-
- this.showFeedback(`Switched to: ${this.currentText.title}`, 'info');
- }
- }
-
- splitIntoSentences(text) {
- // Split by periods, exclamation marks, and question marks
- // Keep the punctuation with the sentence
- const sentences = text.split(/(?<=[.!?])\s+/)
- .filter(sentence => sentence.trim() !== '')
- .map(sentence => sentence.trim());
-
- return sentences.length > 0 ? sentences : [text];
- }
-
- nextSentence() {
- if (this.currentSentenceIndex < this.sentences.length - 1) {
- this.currentSentenceIndex++;
- this.updateDisplay();
- this.updateUI();
- } else {
- // End of sentences, show full text automatically
- this.showFullText();
- }
- }
-
- prevSentence() {
- if (this.currentSentenceIndex > 0) {
- this.currentSentenceIndex--;
- this.updateDisplay();
- this.updateUI();
- }
- }
-
- showFullText() {
- this.showingFullText = true;
- document.getElementById('sentence-display').style.display = 'none';
- document.getElementById('full-text-display').style.display = 'block';
- document.getElementById('full-text-display').innerHTML = `
-
-
-
Original:
-
${this.currentText.original}
-
-
-
Translation:
-
${this.currentText.translation}
-
-
- `;
-
- // Show full text navigation controls
- document.querySelector('.reader-controls').style.display = 'none';
- document.getElementById('full-text-navigation').style.display = 'flex';
-
- this.showFeedback('Full text displayed. Use dropdown to change text.', 'info');
- }
-
- backToReading() {
- this.showingFullText = false;
- document.getElementById('sentence-display').style.display = 'block';
- document.getElementById('full-text-display').style.display = 'none';
-
- // Show sentence navigation controls
- document.querySelector('.reader-controls').style.display = 'flex';
- document.getElementById('full-text-navigation').style.display = 'none';
-
- this.updateDisplay();
- this.updateUI();
- this.showFeedback('Back to sentence-by-sentence reading.', 'info');
- }
-
- // Text navigation methods removed - using dropdown instead
-
- updateDisplay() {
- if (this.showingFullText) return;
-
- const sentenceDisplay = document.getElementById('sentence-display');
- const currentSentence = this.sentences[this.currentSentenceIndex];
-
- sentenceDisplay.innerHTML = `
-
- ${currentSentence}
-
- `;
- }
-
- updateUI() {
- // Update counters
- document.getElementById('sentence-counter').textContent = `${this.currentSentenceIndex + 1} / ${this.sentences.length}`;
-
- // Update button states
- document.getElementById('prev-sentence-btn').disabled = this.currentSentenceIndex === 0;
- document.getElementById('next-sentence-btn').disabled = false;
- document.getElementById('next-sentence-btn').textContent =
- this.currentSentenceIndex === this.sentences.length - 1 ? 'Full Text →' : 'Next →';
- }
-
- // updateTextNavigation method removed - using dropdown instead
-
- showFeedback(message, type = 'info') {
- const feedbackArea = document.getElementById('feedback-area');
- feedbackArea.innerHTML = `${message}
`;
- }
-
- destroy() {
- this.isRunning = false;
- this.container.innerHTML = '';
- }
-}
-
-// Module registration
-window.GameModules = window.GameModules || {};
-window.GameModules.TextReader = TextReaderGame;
\ No newline at end of file
diff --git a/js/games/whack-a-mole-hard.js b/js/games/whack-a-mole-hard.js
index 6ef4355..6eee5dc 100644
--- a/js/games/whack-a-mole-hard.js
+++ b/js/games/whack-a-mole-hard.js
@@ -10,7 +10,7 @@ class WhackAMoleHardGame {
// Game state
this.score = 0;
this.errors = 0;
- this.maxErrors = 5;
+ this.maxErrors = 3;
this.gameTime = 60; // 60 seconds
this.timeLeft = this.gameTime;
this.isRunning = false;
@@ -59,7 +59,7 @@ class WhackAMoleHardGame {
❌ Loading Error
This content does not contain vocabulary compatible with Whack-a-Mole.
The game requires words with their translations.
-
+
`;
}
diff --git a/js/games/whack-a-mole.js b/js/games/whack-a-mole.js
index 733c884..fc69242 100644
--- a/js/games/whack-a-mole.js
+++ b/js/games/whack-a-mole.js
@@ -10,7 +10,7 @@ class WhackAMoleGame {
// Game state
this.score = 0;
this.errors = 0;
- this.maxErrors = 5;
+ this.maxErrors = 3;
this.gameTime = 60; // 60 secondes
this.timeLeft = this.gameTime;
this.isRunning = false;
@@ -58,7 +58,7 @@ class WhackAMoleGame {
❌ Loading Error
This content does not contain vocabulary compatible with Whack-a-Mole.
The game requires words with their translations.
-
+
`;
}
diff --git a/js/games/word-storm.js b/js/games/word-storm.js
index da3b4f4..2d867e6 100644
--- a/js/games/word-storm.js
+++ b/js/games/word-storm.js
@@ -454,7 +454,7 @@ class WordStormGame {
🌪️ Word Storm
❌ No vocabulary found in this content.
This game requires content with vocabulary words.
-
+
`;
diff --git a/test-chinese-beginner-grammar.html b/test-chinese-beginner-grammar.html
deleted file mode 100644
index d87f2c5..0000000
--- a/test-chinese-beginner-grammar.html
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
- Test Chinese Beginner + Grammar Discovery
-
-
-
- 🌸 Test: Chinese Beginner Story + Grammar Discovery
-
-
-
🎯 Test du nouveau module "Le Jardin Magique"
-
Objectif : Vérifier que le contenu chinois → français fonctionne avec Grammar Discovery
-
Attendu : Sélecteur de concept avec "Structure de Phrase de Base"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test-grammar-discovery.html b/test-grammar-discovery.html
deleted file mode 100644
index 6680440..0000000
--- a/test-grammar-discovery.html
+++ /dev/null
@@ -1,92 +0,0 @@
-
-
-
-
- Grammar Discovery Test
-
-
-
- 🎓 Grammar Discovery Game Test
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test-letter-discovery.html b/test-letter-discovery.html
deleted file mode 100644
index 889a0ea..0000000
--- a/test-letter-discovery.html
+++ /dev/null
@@ -1,106 +0,0 @@
-
-
-
-
- Test Letter Discovery Game
-
-
-
- 🔤 Test: Letter Discovery Game
-
-
-
🎯 Testing Letter Discovery with French Beginner Story
-
Flow: Letter discovery → Word exploration → Practice
-
Expected: Letters A, B, C, F, G, J, M, P, R, T, V with their words
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test-river-run.html b/test-river-run.html
deleted file mode 100644
index 8bca8bf..0000000
--- a/test-river-run.html
+++ /dev/null
@@ -1,108 +0,0 @@
-
-
-
-
- Test River Run Game
-
-
-
- 🌊 Test: River Run Game
-
-
-
🎯 Testing River Run with French Beginner Story
-
Gameplay:
-
- - 🛶 Click anywhere to move your boat
- - 🎯 Target word appears at top - find and catch it!
- - ❌ Avoid all other floating words (obstacles)
- - ⚡ Collect power-ups for bonuses
- - 📈 Speed increases as you progress
-
-
Expected: Endless river runner with French vocabulary
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test-word-discovery-quick.html b/test-word-discovery-quick.html
deleted file mode 100644
index 1a03f7f..0000000
--- a/test-word-discovery-quick.html
+++ /dev/null
@@ -1,98 +0,0 @@
-
-
-
-
- Quick Word Discovery Test
-
-
-
- 🔍 Test: Word Discovery with Shuffle
-
-
-
🎯 Testing Word Discovery Game
-
Objective: Verify that Word Discovery loads and shuffles work
-
Expected: Game starts with discovery phase, then shuffled practice
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test-wta1b1.html b/test-wta1b1.html
deleted file mode 100644
index 6f19e03..0000000
--- a/test-wta1b1.html
+++ /dev/null
@@ -1,159 +0,0 @@
-
-
-
-
- Test WTA1B1 Content Module
-
-
-
- 🧪 Test: WTA1B1 Content Module
-
-
-
🎯 Testing WTA1B1 Integration
-
Expected: English letters U, V, T & pets vocabulary with Chinese translation
-
Features: Grammar lessons, vocabulary, story content, corrections
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/tests/fixtures/content-samples.js b/tests/fixtures/content-samples.js
index 59303d9..49e5450 100644
--- a/tests/fixtures/content-samples.js
+++ b/tests/fixtures/content-samples.js
@@ -70,7 +70,7 @@ export const gameCompatibilityTestData = [
vocabulary: { "test": "test" },
texts: [{ title: "Test", content: "Test content" }]
},
- expectedGames: ["whack-a-mole", "memory-match", "quiz-game", "text-reader"]
+ expectedGames: ["whack-a-mole", "memory-match", "quiz-game", "story-reader"]
}
];