From 30a2028da6c57529f774c964d62da1888fb60629 Mon Sep 17 00:00:00 2001 From: StillHammer Date: Sat, 20 Sep 2025 11:22:56 +0800 Subject: [PATCH] Remove Text Reader game and enhance Story Reader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enhanced Story Reader with text-to-story conversion methods - Added support for simple texts and sentences in Story Reader - Removed Text Reader game file (js/games/text-reader.js) - Updated all configuration files to remove Text Reader references - Modified game compatibility system to use Story Reader instead - Updated test fixtures to reflect game changes - Cleaned up debug/test HTML files ๐Ÿค– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- js/core/content-game-compatibility.js | 4 - js/core/content-scanner.js | 9 - js/core/game-loader.js | 2 - js/core/navigation.js | 6 - js/games/adventure-reader.js | 2 +- js/games/chinese-study.js | 6 +- js/games/fill-the-blank.js | 2 +- js/games/memory-match.js | 2 +- js/games/quiz-game.js | 131 ++++++- js/games/story-builder.js | 2 +- js/games/story-reader.js | 130 ++++++- js/games/text-reader.js | 535 -------------------------- js/games/whack-a-mole-hard.js | 4 +- js/games/whack-a-mole.js | 4 +- js/games/word-storm.js | 2 +- test-chinese-beginner-grammar.html | 96 ----- test-grammar-discovery.html | 92 ----- test-letter-discovery.html | 106 ----- test-river-run.html | 108 ------ test-word-discovery-quick.html | 98 ----- test-wta1b1.html | 159 -------- tests/fixtures/content-samples.js | 2 +- 22 files changed, 253 insertions(+), 1249 deletions(-) delete mode 100644 js/games/text-reader.js delete mode 100644 test-chinese-beginner-grammar.html delete mode 100644 test-grammar-discovery.html delete mode 100644 test-letter-discovery.html delete mode 100644 test-river-run.html delete mode 100644 test-word-discovery-quick.html delete mode 100644 test-wta1b1.html 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 -
    -
    - - -
    -
    - -
    - - -
    - - -
    - - - -
    - - - - - - -
    - `; - } - - 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"] } ];