// === STORY BUILDER GAME - CONSTRUCTEUR D'HISTOIRES === class StoryBuilderGame { constructor(options) { this.container = options.container; this.content = options.content; this.contentEngine = options.contentEngine; this.onScoreUpdate = options.onScoreUpdate || (() => {}); this.onGameEnd = options.onGameEnd || (() => {}); // État du jeu this.score = 0; this.currentStory = []; this.availableElements = []; this.storyTarget = null; this.gameMode = 'sequence'; // 'sequence', 'dialogue', 'scenario' // Configuration this.maxElements = 6; this.timeLimit = 180; // 3 minutes this.timeLeft = this.timeLimit; this.isRunning = false; // Timers this.gameTimer = null; this.init(); } init() { this.createGameBoard(); this.setupEventListeners(); this.loadStoryContent(); } createGameBoard() { this.container.innerHTML = `

Objectif:

Choisis un mode et commençons !

${this.timeLeft} Temps
0/${this.maxElements} Progrès
Glisse les éléments ici pour construire ton histoire
Sélectionne un mode pour commencer à construire des histoires !
`; } setupEventListeners() { // Mode selection document.querySelectorAll('.mode-btn').forEach(btn => { btn.addEventListener('click', (e) => { if (this.isRunning) return; document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); this.gameMode = btn.dataset.mode; this.loadStoryContent(); }); }); // Game controls document.getElementById('start-btn').addEventListener('click', () => this.start()); document.getElementById('check-btn').addEventListener('click', () => this.checkStory()); document.getElementById('hint-btn').addEventListener('click', () => this.showHint()); document.getElementById('restart-btn').addEventListener('click', () => this.restart()); // Drag and Drop setup this.setupDragAndDrop(); } loadStoryContent() { if (!this.contentEngine) { console.warn('ContentEngine non disponible, utilisation du contenu de base'); this.setupBasicContent(); return; } // Filtrer le contenu selon le mode const filters = this.getModeFilters(); const filteredContent = this.contentEngine.filterContent(this.content, filters); this.setupContentForMode(filteredContent); } getModeFilters() { switch (this.gameMode) { case 'sequence': return { type: ['sequence', 'vocabulary'] }; case 'dialogue': return { type: ['dialogue', 'sentence'] }; case 'scenario': return { type: ['scenario', 'dialogue', 'sequence'] }; default: return { type: ['vocabulary', 'sentence'] }; } } setupContentForMode(filteredContent) { const contentItems = filteredContent.contentItems || []; switch (this.gameMode) { case 'sequence': this.setupSequenceMode(contentItems); break; case 'dialogue': this.setupDialogueMode(contentItems); break; case 'scenario': this.setupScenarioMode(contentItems); break; } } setupSequenceMode(contentItems) { const sequences = contentItems.filter(item => item.type === 'sequence'); if (sequences.length > 0) { this.storyTarget = sequences[Math.floor(Math.random() * sequences.length)]; this.availableElements = this.shuffleArray([...this.storyTarget.content.steps]); document.getElementById('objective-text').textContent = `Remets en ordre l'histoire: "${this.storyTarget.content.title}"`; } else { this.setupBasicSequence(); } } setupDialogueMode(contentItems) { const dialogues = contentItems.filter(item => item.type === 'dialogue'); if (dialogues.length > 0) { this.storyTarget = dialogues[Math.floor(Math.random() * dialogues.length)]; this.availableElements = this.shuffleArray([...this.storyTarget.content.conversation]); document.getElementById('objective-text').textContent = `Reconstitue le dialogue: "${this.storyTarget.content.english}"`; } else { this.setupBasicDialogue(); } } setupScenarioMode(contentItems) { const scenarios = contentItems.filter(item => item.type === 'scenario'); if (scenarios.length > 0) { this.storyTarget = scenarios[Math.floor(Math.random() * scenarios.length)]; // Mélanger vocabulaire et phrases du scénario const vocabElements = this.storyTarget.content.vocabulary || []; const phraseElements = this.storyTarget.content.phrases || []; this.availableElements = this.shuffleArray([...vocabElements, ...phraseElements]); document.getElementById('objective-text').textContent = `Crée une histoire dans le contexte: "${this.storyTarget.content.english}"`; } else { this.setupBasicScenario(); } } setupBasicContent() { // Fallback pour l'ancien format const vocabulary = this.content.vocabulary || []; this.availableElements = vocabulary.slice(0, 6); this.gameMode = 'vocabulary'; document.getElementById('objective-text').textContent = 'Construis une histoire avec ces mots !'; } start() { if (this.isRunning || this.availableElements.length === 0) return; this.isRunning = true; this.score = 0; this.currentStory = []; this.timeLeft = this.timeLimit; this.renderElements(); this.startTimer(); this.updateUI(); document.getElementById('start-btn').disabled = true; document.getElementById('check-btn').disabled = false; document.getElementById('hint-btn').disabled = false; this.showFeedback('Glisse les éléments dans l\'ordre pour construire ton histoire !', 'info'); } renderElements() { const elementsBank = document.getElementById('elements-bank'); elementsBank.innerHTML = '

Éléments disponibles:

'; this.availableElements.forEach((element, index) => { const elementDiv = this.createElement(element, index); elementsBank.appendChild(elementDiv); }); } createElement(element, index) { const div = document.createElement('div'); div.className = 'story-element'; div.draggable = true; div.dataset.index = index; // Adapter l'affichage selon le type d'élément if (element.english && element.french) { // Vocabulaire ou phrase div.innerHTML = `
${element.english}
${element.french}
`; } else if (element.text || element.english) { // Dialogue ou séquence div.innerHTML = `
${element.text || element.english}
${element.french ? `
${element.french}
` : ''}
`; } else if (typeof element === 'string') { // Texte simple div.innerHTML = `
${element}
`; } if (element.icon) { div.innerHTML = `${element.icon}` + div.innerHTML; } return div; } setupDragAndDrop() { let draggedElement = null; document.addEventListener('dragstart', (e) => { if (e.target.classList.contains('story-element')) { draggedElement = e.target; e.target.style.opacity = '0.5'; } }); document.addEventListener('dragend', (e) => { if (e.target.classList.contains('story-element')) { e.target.style.opacity = '1'; draggedElement = null; } }); const dropZone = document.getElementById('drop-zone'); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('drag-over'); }); dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('drag-over'); }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('drag-over'); if (draggedElement && this.isRunning) { this.addToStory(draggedElement); } }); } addToStory(elementDiv) { const index = parseInt(elementDiv.dataset.index); const element = this.availableElements[index]; // Ajouter à l'histoire this.currentStory.push({ element, originalIndex: index }); // Créer élément dans la zone de construction const storyElement = elementDiv.cloneNode(true); storyElement.classList.add('in-story'); storyElement.draggable = false; // Ajouter bouton de suppression const removeBtn = document.createElement('button'); removeBtn.className = 'remove-element'; removeBtn.innerHTML = '×'; removeBtn.onclick = () => this.removeFromStory(storyElement, element); storyElement.appendChild(removeBtn); document.getElementById('drop-zone').appendChild(storyElement); // Masquer l'élément original elementDiv.style.display = 'none'; this.updateProgress(); } removeFromStory(storyElement, element) { // Supprimer de l'histoire this.currentStory = this.currentStory.filter(item => item.element !== element); // Supprimer visuellement storyElement.remove(); // Réafficher l'élément original const originalElement = document.querySelector(`[data-index="${this.availableElements.indexOf(element)}"]`); if (originalElement) { originalElement.style.display = 'block'; } this.updateProgress(); } checkStory() { if (this.currentStory.length === 0) { this.showFeedback('Ajoute au moins un élément à ton histoire !', 'error'); return; } const isCorrect = this.validateStory(); if (isCorrect) { this.score += this.currentStory.length * 10; this.showFeedback('Bravo ! Histoire parfaite ! 🎉', 'success'); this.onScoreUpdate(this.score); setTimeout(() => { this.nextChallenge(); }, 2000); } else { this.score = Math.max(0, this.score - 5); this.showFeedback('Presque ! Vérifie l\'ordre de ton histoire 🤔', 'warning'); this.onScoreUpdate(this.score); } } validateStory() { switch (this.gameMode) { case 'sequence': return this.validateSequence(); case 'dialogue': return this.validateDialogue(); case 'scenario': return this.validateScenario(); default: return true; // Mode libre } } validateSequence() { if (!this.storyTarget?.content?.steps) return true; const expectedOrder = this.storyTarget.content.steps.sort((a, b) => a.order - b.order); if (this.currentStory.length !== expectedOrder.length) return false; return this.currentStory.every((item, index) => { const expected = expectedOrder[index]; return item.element.order === expected.order; }); } validateDialogue() { // Validation flexible du dialogue (ordre logique des répliques) return this.currentStory.length >= 2; } validateScenario() { // Validation flexible du scénario (cohérence contextuelle) return this.currentStory.length >= 3; } showHint() { if (!this.storyTarget) { this.showFeedback('Astuce : Pense à l\'ordre logique des événements !', 'info'); return; } switch (this.gameMode) { case 'sequence': if (this.storyTarget.content?.steps) { const nextStep = this.storyTarget.content.steps.find(step => !this.currentStory.some(item => item.element.order === step.order) ); if (nextStep) { this.showFeedback(`Prochaine étape : "${nextStep.english}"`, 'info'); } } break; case 'dialogue': this.showFeedback('Pense à l\'ordre naturel d\'une conversation !', 'info'); break; case 'scenario': this.showFeedback('Crée une histoire cohérente dans ce contexte !', 'info'); break; } } nextChallenge() { // Charger un nouveau défi this.loadStoryContent(); this.currentStory = []; document.getElementById('drop-zone').innerHTML = '
Glisse les éléments ici pour construire ton histoire
'; this.renderElements(); this.updateProgress(); } startTimer() { this.gameTimer = setInterval(() => { this.timeLeft--; this.updateUI(); if (this.timeLeft <= 0) { this.endGame(); } }, 1000); } endGame() { this.isRunning = false; if (this.gameTimer) { clearInterval(this.gameTimer); this.gameTimer = null; } document.getElementById('start-btn').disabled = false; document.getElementById('check-btn').disabled = true; document.getElementById('hint-btn').disabled = true; this.onGameEnd(this.score); } restart() { this.endGame(); this.score = 0; this.currentStory = []; this.timeLeft = this.timeLimit; this.onScoreUpdate(0); document.getElementById('drop-zone').innerHTML = '
Glisse les éléments ici pour construire ton histoire
'; this.loadStoryContent(); this.updateUI(); } updateProgress() { document.getElementById('story-progress').textContent = `${this.currentStory.length}/${this.maxElements}`; } updateUI() { document.getElementById('time-left').textContent = this.timeLeft; } showFeedback(message, type = 'info') { const feedbackArea = document.getElementById('feedback-area'); feedbackArea.innerHTML = `
${message}
`; } 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.endGame(); this.container.innerHTML = ''; } } // CSS pour Story Builder const storyBuilderStyles = ` `; // Ajouter les styles document.head.insertAdjacentHTML('beforeend', storyBuilderStyles); // Enregistrement du module window.GameModules = window.GameModules || {}; window.GameModules.StoryBuilder = StoryBuilderGame;