Class_generator/js/games/fill-the-blank.js

419 lines
16 KiB
JavaScript

// === MODULE FILL THE BLANK ===
class FillTheBlankGame {
constructor(options) {
this.container = options.container;
this.content = options.content;
this.onScoreUpdate = options.onScoreUpdate || (() => {});
this.onGameEnd = options.onGameEnd || (() => {});
// État du jeu
this.score = 0;
this.errors = 0;
this.currentSentenceIndex = 0;
this.isRunning = false;
// Données de jeu
this.sentences = this.extractSentences(this.content);
this.currentSentence = null;
this.blanks = [];
this.userAnswers = [];
this.init();
}
init() {
// Vérifier que nous avons des phrases
if (!this.sentences || this.sentences.length === 0) {
console.error('Aucune phrase disponible pour Fill the Blank');
this.showInitError();
return;
}
this.createGameBoard();
this.setupEventListeners();
// Le jeu démarrera quand start() sera appelé
}
showInitError() {
this.container.innerHTML = `
<div class="game-error">
<h3>❌ Erreur de chargement</h3>
<p>Ce contenu ne contient pas de phrases compatibles avec Fill the Blank.</p>
<p>Le jeu nécessite des phrases avec leurs traductions.</p>
<button onclick="AppNavigation.goBack()" class="back-btn">← Retour</button>
</div>
`;
}
extractSentences(content) {
let sentences = [];
console.log('🔍 Extraction phrases depuis:', content?.name || 'contenu');
// Utiliser le contenu brut du module si disponible
if (content.rawContent) {
console.log('📦 Utilisation du contenu brut du module');
return this.extractSentencesFromRaw(content.rawContent);
}
// Format avec sentences array
if (content.sentences && Array.isArray(content.sentences)) {
console.log('📝 Format sentences détecté');
sentences = content.sentences.filter(sentence =>
sentence.english && sentence.english.trim() !== ''
);
}
// Format moderne avec contentItems
else if (content.contentItems && Array.isArray(content.contentItems)) {
console.log('🆕 Format contentItems détecté');
sentences = content.contentItems
.filter(item => item.type === 'sentence' && item.english)
.map(item => ({
english: item.english,
french: item.french || item.translation,
chinese: item.chinese
}));
}
return this.finalizeSentences(sentences);
}
extractSentencesFromRaw(rawContent) {
console.log('🔧 Extraction depuis contenu brut:', rawContent.name || 'Module');
let sentences = [];
// Format simple (sentences array)
if (rawContent.sentences && Array.isArray(rawContent.sentences)) {
sentences = rawContent.sentences.filter(sentence =>
sentence.english && sentence.english.trim() !== ''
);
console.log(`📝 ${sentences.length} phrases extraites depuis sentences array`);
}
// Format contentItems
else if (rawContent.contentItems && Array.isArray(rawContent.contentItems)) {
sentences = rawContent.contentItems
.filter(item => item.type === 'sentence' && item.english)
.map(item => ({
english: item.english,
french: item.french || item.translation,
chinese: item.chinese
}));
console.log(`🆕 ${sentences.length} phrases extraites depuis contentItems`);
}
return this.finalizeSentences(sentences);
}
finalizeSentences(sentences) {
// Validation et nettoyage
sentences = sentences.filter(sentence =>
sentence &&
typeof sentence.english === 'string' &&
sentence.english.trim() !== '' &&
sentence.english.split(' ').length >= 3 // Au moins 3 mots pour créer des blanks
);
if (sentences.length === 0) {
console.error('❌ Aucune phrase valide trouvée');
// Phrases de démonstration en dernier recours
sentences = [
{ english: "I am learning English.", chinese: "我正在学英语。" },
{ english: "She goes to school every day.", chinese: "她每天都去学校。" },
{ english: "We like to play games together.", chinese: "我们喜欢一起玩游戏。" }
];
console.warn('🚨 Utilisation de phrases de démonstration');
}
// Mélanger les phrases
sentences = this.shuffleArray(sentences);
console.log(`✅ Fill the Blank: ${sentences.length} phrases finalisées`);
return sentences;
}
createGameBoard() {
this.container.innerHTML = `
<div class="fill-blank-wrapper">
<!-- Game Info -->
<div class="game-info">
<div class="game-stats">
<div class="stat-item">
<span class="stat-value" id="current-question">${this.currentSentenceIndex + 1}</span>
<span class="stat-label">/ ${this.sentences.length}</span>
</div>
<div class="stat-item">
<span class="stat-value" id="errors-count">${this.errors}</span>
<span class="stat-label">Erreurs</span>
</div>
<div class="stat-item">
<span class="stat-value" id="score-display">${this.score}</span>
<span class="stat-label">Score</span>
</div>
</div>
</div>
<!-- Translation hint -->
<div class="translation-hint" id="translation-hint">
<!-- La traduction apparaîtra ici -->
</div>
<!-- Sentence with blanks -->
<div class="sentence-container" id="sentence-container">
<!-- La phrase avec les blanks apparaîtra ici -->
</div>
<!-- Input area -->
<div class="input-area" id="input-area">
<!-- Les inputs apparaîtront ici -->
</div>
<!-- Controls -->
<div class="game-controls">
<button class="control-btn secondary" id="hint-btn">💡 Indice</button>
<button class="control-btn primary" id="check-btn">✓ Vérifier</button>
<button class="control-btn secondary" id="skip-btn">→ Suivant</button>
</div>
<!-- Feedback Area -->
<div class="feedback-area" id="feedback-area">
<div class="instruction">
Complète la phrase en remplissant les blancs !
</div>
</div>
</div>
`;
}
setupEventListeners() {
document.getElementById('check-btn').addEventListener('click', () => this.checkAnswer());
document.getElementById('hint-btn').addEventListener('click', () => this.showHint());
document.getElementById('skip-btn').addEventListener('click', () => this.skipSentence());
// Enter key to check answer
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && this.isRunning) {
this.checkAnswer();
}
});
}
start() {
console.log('🎮 Fill the Blank: Démarrage du jeu');
this.loadNextSentence();
}
restart() {
console.log('🔄 Fill the Blank: Redémarrage du jeu');
this.reset();
this.start();
}
reset() {
this.score = 0;
this.errors = 0;
this.currentSentenceIndex = 0;
this.isRunning = false;
this.currentSentence = null;
this.blanks = [];
this.userAnswers = [];
this.onScoreUpdate(0);
}
loadNextSentence() {
// Si on a fini toutes les phrases, recommencer depuis le début
if (this.currentSentenceIndex >= this.sentences.length) {
this.currentSentenceIndex = 0;
this.sentences = this.shuffleArray(this.sentences); // Mélanger à nouveau
this.showFeedback(`🎉 Toutes les phrases terminées ! On recommence avec un nouvel ordre.`, 'success');
setTimeout(() => {
this.loadNextSentence();
}, 1500);
return;
}
this.isRunning = true;
this.currentSentence = this.sentences[this.currentSentenceIndex];
this.createBlanks();
this.displaySentence();
this.updateUI();
}
createBlanks() {
const words = this.currentSentence.english.split(' ');
this.blanks = [];
// Créer 1-3 blanks selon la longueur de la phrase
const numBlanks = Math.min(Math.max(1, Math.floor(words.length / 4)), 3);
const blankIndices = new Set();
// Sélectionner des mots aléatoires (pas les articles/prépositions courtes)
const candidateWords = words.map((word, index) => ({ word, index }))
.filter(item => item.word.length > 2 && !['the', 'and', 'but', 'for', 'nor', 'or', 'so', 'yet'].includes(item.word.toLowerCase()));
// Si pas assez de candidats, prendre n'importe quels mots
if (candidateWords.length < numBlanks) {
candidateWords = words.map((word, index) => ({ word, index }));
}
// Sélectionner aléatoirement les indices des blanks
const shuffledCandidates = this.shuffleArray(candidateWords);
for (let i = 0; i < Math.min(numBlanks, shuffledCandidates.length); i++) {
blankIndices.add(shuffledCandidates[i].index);
}
// Créer la structure des blanks
words.forEach((word, index) => {
if (blankIndices.has(index)) {
this.blanks.push({
index: index,
word: word.replace(/[.,!?;:]$/, ''), // Retirer la ponctuation
punctuation: word.match(/[.,!?;:]$/) ? word.match(/[.,!?;:]$/)[0] : '',
userAnswer: ''
});
}
});
}
displaySentence() {
const words = this.currentSentence.english.split(' ');
let sentenceHTML = '';
let blankCounter = 0;
words.forEach((word, index) => {
const blank = this.blanks.find(b => b.index === index);
if (blank) {
sentenceHTML += `<span class="blank-wrapper">
<input type="text" class="blank-input"
id="blank-${blankCounter}"
placeholder="___"
maxlength="${blank.word.length + 2}">
${blank.punctuation}
</span> `;
blankCounter++;
} else {
sentenceHTML += `<span class="word">${word}</span> `;
}
});
document.getElementById('sentence-container').innerHTML = sentenceHTML;
// Afficher la traduction si disponible
const translation = this.currentSentence.chinese || this.currentSentence.french || '';
document.getElementById('translation-hint').innerHTML = translation ?
`<em>💭 ${translation}</em>` : '';
// Focus sur le premier input
const firstInput = document.getElementById('blank-0');
if (firstInput) {
setTimeout(() => firstInput.focus(), 100);
}
}
checkAnswer() {
if (!this.isRunning) return;
let allCorrect = true;
let correctCount = 0;
// Vérifier chaque blank
this.blanks.forEach((blank, index) => {
const input = document.getElementById(`blank-${index}`);
const userAnswer = input.value.trim().toLowerCase();
const correctAnswer = blank.word.toLowerCase();
blank.userAnswer = input.value.trim();
if (userAnswer === correctAnswer) {
input.classList.remove('incorrect');
input.classList.add('correct');
correctCount++;
} else {
input.classList.remove('correct');
input.classList.add('incorrect');
allCorrect = false;
}
});
if (allCorrect) {
// Toutes les réponses sont correctes
this.score += 10 * this.blanks.length;
this.showFeedback(`🎉 Parfait ! +${10 * this.blanks.length} points`, 'success');
setTimeout(() => {
this.currentSentenceIndex++;
this.loadNextSentence();
}, 1500);
} else {
// Quelques erreurs
this.errors++;
if (correctCount > 0) {
this.score += 5 * correctCount;
this.showFeedback(`${correctCount}/${this.blanks.length} correct ! +${5 * correctCount} points. Essaye encore.`, 'partial');
} else {
this.showFeedback(`❌ Essaye encore ! (${this.errors} erreurs)`, 'error');
}
}
this.updateUI();
this.onScoreUpdate(this.score);
}
showHint() {
// Afficher la première lettre de chaque blank vide
this.blanks.forEach((blank, index) => {
const input = document.getElementById(`blank-${index}`);
if (!input.value.trim()) {
input.value = blank.word[0];
input.focus();
}
});
this.showFeedback('💡 Première lettre ajoutée !', 'info');
}
skipSentence() {
// Révéler les bonnes réponses
this.blanks.forEach((blank, index) => {
const input = document.getElementById(`blank-${index}`);
input.value = blank.word;
input.classList.add('revealed');
});
this.showFeedback('📖 Réponses révélées ! Phrase suivante...', 'info');
setTimeout(() => {
this.currentSentenceIndex++;
this.loadNextSentence();
}, 2000);
}
// Méthode endGame supprimée - le jeu continue indéfiniment
showFeedback(message, type = 'info') {
const feedbackArea = document.getElementById('feedback-area');
feedbackArea.innerHTML = `<div class="instruction ${type}">${message}</div>`;
}
updateUI() {
document.getElementById('current-question').textContent = this.currentSentenceIndex + 1;
document.getElementById('errors-count').textContent = this.errors;
document.getElementById('score-display').textContent = this.score;
}
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;
}
destroy() {
this.isRunning = false;
this.container.innerHTML = '';
}
}
// Enregistrement du module
window.GameModules = window.GameModules || {};
window.GameModules.FillTheBlank = FillTheBlankGame;