// === WIZARD SPELL CASTER GAME === // Advanced game for 11+ years old - Form sentences to cast magical spells class WizardSpellCaster { constructor({ container, content, onScoreUpdate, onGameEnd }) { this.container = container; this.content = content; this.onScoreUpdate = onScoreUpdate; this.onGameEnd = onGameEnd; this.score = 0; this.enemyHP = 100; this.playerHP = 100; this.currentSpells = []; this.selectedWords = []; // Timer invisible pour bonus de vitesse this.spellStartTime = null; this.averageSpellTime = 0; this.spellCount = 0; // Enemy attack system this.enemyAttackTimer = null; this.nextEnemyAttack = this.getRandomAttackTime(); this.injectCSS(); this.extractSpells(); this.init(); } injectCSS() { if (document.getElementById('wizard-spell-caster-styles')) return; const styleSheet = document.createElement('style'); styleSheet.id = 'wizard-spell-caster-styles'; styleSheet.textContent = ` .wizard-game-wrapper { background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); min-height: 100vh; color: white; font-family: 'Fantasy', serif; position: relative; overflow: hidden; } .wizard-hud { display: flex; justify-content: space-between; padding: 15px; background: rgba(0,0,0,0.3); border-bottom: 2px solid #ffd700; } .wizard-stats { display: flex; gap: 20px; align-items: center; } .health-bar { width: 150px; height: 20px; background: rgba(255,255,255,0.2); border-radius: 10px; overflow: hidden; border: 2px solid #ffd700; } .health-fill { height: 100%; background: linear-gradient(90deg, #ff4757, #ff6b7a); transition: width 0.3s ease; } .battle-area { display: flex; height: 60vh; padding: 20px; } .wizard-side { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; } .enemy-side { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; } .wizard-character { width: 120px; height: 120px; background: linear-gradient(45deg, #6c5ce7, #a29bfe); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 48px; margin-bottom: 20px; animation: float 3s ease-in-out infinite; box-shadow: 0 0 30px rgba(108, 92, 231, 0.6); } .enemy-character { width: 150px; height: 150px; background: linear-gradient(45deg, #ff4757, #ff6b7a); border-radius: 20px; display: flex; align-items: center; justify-content: center; font-size: 64px; margin-bottom: 20px; animation: enemyPulse 2s ease-in-out infinite; box-shadow: 0 0 40px rgba(255, 71, 87, 0.6); } @keyframes float { 0%, 100% { transform: translateY(0px); } 50% { transform: translateY(-10px); } } @keyframes enemyPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } } .spell-casting-area { background: rgba(0,0,0,0.4); border: 2px solid #ffd700; border-radius: 15px; padding: 20px; margin: 20px; } .spell-selection { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin-bottom: 20px; } .spell-card { background: linear-gradient(135deg, #2c2c54, #40407a); border: 2px solid #ffd700; border-radius: 10px; padding: 15px; cursor: pointer; transition: all 0.3s ease; text-align: center; } .spell-card:hover { transform: translateY(-5px); box-shadow: 0 10px 25px rgba(255, 215, 0, 0.3); border-color: #fff; } .spell-card.selected { background: linear-gradient(135deg, #ffd700, #ffed4e); color: #000; transform: scale(1.05); } .spell-type { font-size: 12px; color: #ffd700; font-weight: bold; margin-bottom: 5px; } .spell-damage { font-size: 14px; color: #ff6b7a; font-weight: bold; } .sentence-builder { background: rgba(255,255,255,0.1); border-radius: 10px; padding: 15px; margin-bottom: 20px; min-height: 80px; border: 2px dashed #ffd700; } .word-bank { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 20px; } .word-tile { background: linear-gradient(135deg, #5f27cd, #8854d0); color: white; padding: 8px 15px; border-radius: 20px; cursor: grab; user-select: none; transition: all 0.3s ease; border: 2px solid transparent; } .word-tile:hover { transform: scale(1.1); box-shadow: 0 5px 15px rgba(95, 39, 205, 0.4); } .word-tile.selected { background: linear-gradient(135deg, #ffd700, #ffed4e); color: #000; border-color: #fff; } .word-tile:active { cursor: grabbing; } .cast-button { background: linear-gradient(135deg, #ff6b7a, #ff4757); border: none; color: white; padding: 15px 30px; border-radius: 25px; font-size: 18px; font-weight: bold; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 5px 15px rgba(255, 71, 87, 0.3); width: 100%; } .cast-button:hover { transform: translateY(-3px); box-shadow: 0 8px 25px rgba(255, 71, 87, 0.5); } .cast-button:disabled { background: #666; cursor: not-allowed; transform: none; box-shadow: none; } .damage-number { position: absolute; font-size: 36px; font-weight: bold; color: #ff4757; text-shadow: 2px 2px 4px rgba(0,0,0,0.8); pointer-events: none; animation: damageFloat 1.5s ease-out forwards; } @keyframes damageFloat { 0% { opacity: 1; transform: translateY(0) scale(1); } 100% { opacity: 0; transform: translateY(-100px) scale(1.5); } } .spell-effect { position: absolute; width: 100px; height: 100px; border-radius: 50%; pointer-events: none; animation: spellBlast 0.8s ease-out forwards; } .fire-effect { background: radial-gradient(circle, #ff6b7a, #ff4757, transparent); } .lightning-effect { background: radial-gradient(circle, #ffd700, #ffed4e, transparent); } .meteor-effect { background: radial-gradient(circle, #a29bfe, #6c5ce7, transparent); } @keyframes spellBlast { 0% { transform: scale(0); opacity: 1; } 50% { transform: scale(1.5); opacity: 0.8; } 100% { transform: scale(3); opacity: 0; } } .mini-enemy { position: absolute; width: 60px; height: 60px; background: linear-gradient(45deg, #ff9ff3, #f368e0); border-radius: 50%; font-size: 30px; display: flex; align-items: center; justify-content: center; animation: miniEnemyFloat 3s ease-in-out infinite; z-index: 100; } @keyframes miniEnemyFloat { 0%, 100% { transform: translateY(0px) rotate(0deg); } 50% { transform: translateY(-15px) rotate(180deg); } } .magic-quirk { position: fixed; width: 200px; height: 200px; border-radius: 50%; background: conic-gradient(from 0deg, #ff0080, #0080ff, #ff0080); animation: magicQuirk 2s ease-in-out; z-index: 1000; pointer-events: none; } @keyframes magicQuirk { 0% { transform: translate(-50%, -50%) scale(0) rotate(0deg); opacity: 1; } 50% { transform: translate(-50%, -50%) scale(1.5) rotate(180deg); opacity: 0.8; } 100% { transform: translate(-50%, -50%) scale(0) rotate(360deg); opacity: 0; } } .flying-bird { position: fixed; font-size: 48px; /* Plus gros ! */ z-index: 500; pointer-events: none; width: 60px; /* Plus gros ! */ height: 60px; /* Plus gros ! */ display: flex; align-items: center; justify-content: center; } .bird-path-1 { animation: flyPath1 8s linear infinite; /* Plus rapide ! */ } .bird-path-2 { animation: flyPath2 6s linear infinite; /* Plus rapide ! */ } .bird-path-3 { animation: flyPath3 10s linear infinite; /* Plus rapide ! */ } .bird-path-4 { animation: flyPath4 12s linear infinite; /* Nouveau chemin ! */ } .bird-path-5 { animation: flyPath5 9s linear infinite; /* Encore un autre ! */ } @keyframes flyPath1 { 0% { left: -100px; top: 20vh; transform: rotate(0deg) scale(1); } 15% { left: 30vw; top: 5vh; transform: rotate(180deg) scale(1.5); /* Rotation plus douce */ } 30% { left: 70vw; top: 40vh; transform: rotate(-90deg) scale(0.5); } 45% { left: 100vw; top: 15vh; transform: rotate(270deg) scale(2); } 60% { left: 60vw; top: 85vh; transform: rotate(-180deg) scale(0.8); } 80% { left: 10vw; top: 60vh; transform: rotate(360deg) scale(1.2); } 100% { left: -100px; top: 20vh; transform: rotate(450deg) scale(1); } } @keyframes flyPath2 { 0% { left: 50vw; top: -80px; transform: rotate(0deg) scale(0.5); } 20% { left: 80vw; top: 20vh; transform: rotate(-225deg) scale(2.5); } 40% { left: 20vw; top: 50vh; transform: rotate(315deg) scale(0.3); } 60% { left: 90vw; top: 80vh; transform: rotate(-450deg) scale(3); } 80% { left: 30vw; top: 30vh; transform: rotate(540deg) scale(0.7); } 100% { left: 50vw; top: -80px; transform: rotate(-630deg) scale(0.5); } } @keyframes flyPath3 { 0% { left: 120vw; top: 10vh; transform: rotate(0deg) scale(1); } 12% { left: 75vw; top: 70vh; transform: rotate(-360deg) scale(4); } 25% { left: 25vw; top: 20vh; transform: rotate(540deg) scale(0.2); } 37% { left: 85vw; top: 90vh; transform: rotate(-720deg) scale(3.5); } 50% { left: 10vw; top: 5vh; transform: rotate(900deg) scale(0.4); } 62% { left: 90vw; top: 55vh; transform: rotate(-1080deg) scale(2.8); } 75% { left: 40vw; top: 95vh; transform: rotate(1260deg) scale(0.6); } 87% { left: 70vw; top: 25vh; transform: rotate(-1440deg) scale(3.2); } 100% { left: 120vw; top: 10vh; transform: rotate(1800deg) scale(1); } } @keyframes flyPath4 { 0% { left: 25vw; top: 100vh; transform: rotate(0deg) scale(1.2); } 20% { left: 75vw; top: 80vh; transform: rotate(180deg) scale(0.7); } 40% { left: 90vw; top: 40vh; transform: rotate(-270deg) scale(2.2); } 60% { left: 40vw; top: 20vh; transform: rotate(360deg) scale(0.4); } 80% { left: 10vw; top: 70vh; transform: rotate(-450deg) scale(1.8); } 100% { left: 25vw; top: 100vh; transform: rotate(540deg) scale(1.2); } } @keyframes flyPath5 { 0% { left: 100vw; top: 50vh; transform: rotate(45deg) scale(0.8); } 25% { left: 60vw; top: 10vh; transform: rotate(-135deg) scale(2.5); } 50% { left: 20vw; top: 80vh; transform: rotate(225deg) scale(0.3); } 75% { left: 80vw; top: 90vh; transform: rotate(-315deg) scale(3); } 100% { left: 100vw; top: 50vh; transform: rotate(405deg) scale(0.8); } } .screen-shake { animation: screenShake 0.5s ease-in-out; } @keyframes screenShake { 0%, 100% { transform: translateX(0); } 10% { transform: translateX(-10px); } 20% { transform: translateX(10px); } 30% { transform: translateX(-10px); } 40% { transform: translateX(10px); } 50% { transform: translateX(-10px); } 60% { transform: translateX(10px); } 70% { transform: translateX(-10px); } 80% { transform: translateX(10px); } 90% { transform: translateX(-10px); } } .enemy-attack-warning { position: absolute; top: -30px; left: 50%; transform: translateX(-50%); background: #ff4757; color: white; padding: 5px 15px; border-radius: 15px; font-size: 14px; font-weight: bold; animation: warningPulse 1s ease-in-out infinite; z-index: 100; } @keyframes warningPulse { 0%, 100% { opacity: 1; transform: translateX(-50%) scale(1); } 50% { opacity: 0.6; transform: translateX(-50%) scale(1.1); } } .enemy-attack-effect { position: absolute; width: 150px; height: 150px; border-radius: 50%; background: radial-gradient(circle, #ff4757, transparent); animation: enemyAttackBlast 1s ease-out; pointer-events: none; z-index: 200; } @keyframes enemyAttackBlast { 0% { transform: scale(0); opacity: 1; } 50% { transform: scale(1.5); opacity: 0.8; } 100% { transform: scale(3); opacity: 0; } } .enemy-charging { animation: enemyCharging 2s ease-in-out; } @keyframes enemyCharging { 0%, 100% { background: linear-gradient(45deg, #ff4757, #ff6b7a); transform: scale(1); } 50% { background: linear-gradient(45deg, #ff0000, #ff3333); transform: scale(1.1); box-shadow: 0 0 60px rgba(255, 0, 0, 0.8); } } .victory-screen, .defeat-screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 1000; } .result-title { font-size: 48px; margin-bottom: 20px; text-shadow: 2px 2px 4px rgba(0,0,0,0.8); } .victory-screen .result-title { color: #2ed573; } .defeat-screen .result-title { color: #ff4757; } .fail-message { position: fixed; top: 30%; left: 50%; transform: translateX(-50%); background: rgba(255, 71, 87, 0.9); color: white; padding: 20px 30px; border-radius: 15px; font-size: 24px; font-weight: bold; z-index: 1000; animation: failMessagePop 2s ease-out; text-align: center; border: 3px solid #ffd700; } @keyframes failMessagePop { 0% { transform: translateX(-50%) scale(0); opacity: 0; } 20% { transform: translateX(-50%) scale(1.2); opacity: 1; } 80% { transform: translateX(-50%) scale(1); opacity: 1; } 100% { transform: translateX(-50%) scale(0.8); opacity: 0; } } /* === ENHANCED SPELL EFFECTS === */ /* Particle animations */ @keyframes fireParticle { 0% { transform: scale(1) translate(0, 0); opacity: 1; } 50% { transform: scale(1.5) translate(var(--random-x, 20px), var(--random-y, -30px)); opacity: 0.8; } 100% { transform: scale(0.5) translate(var(--random-x, 40px), var(--random-y, -60px)); opacity: 0; } } @keyframes lightningParticle { 0% { transform: scale(1) translate(0, 0); opacity: 1; filter: brightness(2); } 25% { transform: scale(2) translate(var(--random-x, 10px), var(--random-y, -20px)); opacity: 1; filter: brightness(3); } 100% { transform: scale(0) translate(var(--random-x, 30px), var(--random-y, -50px)); opacity: 0; filter: brightness(1); } } @keyframes meteorParticle { 0% { transform: scale(0.5) translate(0, -100px); opacity: 0.5; } 30% { transform: scale(1.2) translate(var(--random-x, 0px), 0); opacity: 1; } 100% { transform: scale(0.3) translate(var(--random-x, 20px), var(--random-y, 50px)); opacity: 0; } } /* Screen effects */ @keyframes meteorTrail { 0% { opacity: 0; transform: translateX(100px) scaleY(0); } 50% { opacity: 1; transform: translateX(0) scaleY(1); } 100% { opacity: 0; transform: translateX(-100px) scaleY(0.5); } } @keyframes lightningFlash { 0% { opacity: 0; } 50% { opacity: 0.8; } 100% { opacity: 0; } } @keyframes fireRipple { 0% { transform: scale(0.5); opacity: 1; border-width: 3px; } 50% { transform: scale(1.2); opacity: 0.6; border-width: 2px; } 100% { transform: scale(2); opacity: 0; border-width: 1px; } } /* Enhanced spell effect improvements */ .fire-effect { background: radial-gradient(circle, #ff6b7a, #ff4757, #ff3742, transparent); filter: drop-shadow(0 0 20px #ff4757); animation: spellBlast 0.8s ease-out forwards, fireGlow 0.8s ease-out; } .lightning-effect { background: radial-gradient(circle, #ffd700, #ffed4e, #fff200, transparent); filter: drop-shadow(0 0 25px #ffd700); animation: spellBlast 0.8s ease-out forwards, lightningPulse 0.8s ease-out; } .meteor-effect { background: radial-gradient(circle, #a29bfe, #6c5ce7, #5f3dc4, transparent); filter: drop-shadow(0 0 30px #6c5ce7); animation: spellBlast 0.8s ease-out forwards, meteorImpact 0.8s ease-out; } @keyframes fireGlow { 0%, 100% { filter: drop-shadow(0 0 20px #ff4757) hue-rotate(0deg); } 50% { filter: drop-shadow(0 0 40px #ff4757) hue-rotate(30deg); } } @keyframes lightningPulse { 0%, 100% { filter: drop-shadow(0 0 25px #ffd700) brightness(1); } 50% { filter: drop-shadow(0 0 50px #ffd700) brightness(2); } } @keyframes meteorImpact { 0% { filter: drop-shadow(0 0 30px #6c5ce7) contrast(1); } 30% { filter: drop-shadow(0 0 60px #6c5ce7) contrast(1.5); } 100% { filter: drop-shadow(0 0 30px #6c5ce7) contrast(1); } } /* Spell casting enhancement */ .spell-card.selected { animation: spellCharging 0.5s ease-in-out infinite alternate; } @keyframes spellCharging { 0% { box-shadow: 0 0 20px rgba(255, 215, 0, 0.5); transform: scale(1); } 100% { box-shadow: 0 0 40px rgba(255, 215, 0, 0.8); transform: scale(1.02); } } /* Casting effect animations */ @keyframes magicCircleForm { 0% { transform: scale(0.5) rotate(0deg); opacity: 0; } 50% { transform: scale(1.1) rotate(180deg); opacity: 1; } 100% { transform: scale(1) rotate(360deg); opacity: 0; } } @keyframes castingSparkle { 0% { transform: scale(1) rotate(0deg); opacity: 1; } 50% { transform: scale(1.5) rotate(180deg); opacity: 0.8; } 100% { transform: scale(0.5) rotate(360deg); opacity: 0; } } `; document.head.appendChild(styleSheet); } extractSpells() { // Extract sentences from content and categorize by length this.spells = { short: [], // 3-4 words medium: [], // 5-6 words long: [] // 7+ words }; // Process story sentences if (this.content.story && this.content.story.chapters) { this.content.story.chapters.forEach(chapter => { chapter.sentences.forEach(sentence => { const wordCount = sentence.words.length; const spellData = { english: sentence.original, translation: sentence.translation, words: sentence.words, damage: this.calculateDamage(wordCount), castTime: this.calculateCastTime(wordCount) }; if (wordCount <= 4) { this.spells.short.push(spellData); } else if (wordCount <= 6) { this.spells.medium.push(spellData); } else { this.spells.long.push(spellData); } }); }); } console.log('Spells extracted:', this.spells); } calculateDamage(wordCount) { // Augmenter significativement les points pour les phrases longues if (wordCount <= 3) return Math.floor(Math.random() * 10) + 15; // 15-25 (phrases courtes) if (wordCount <= 5) return Math.floor(Math.random() * 15) + 30; // 30-45 (phrases moyennes) if (wordCount <= 7) return Math.floor(Math.random() * 20) + 50; // 50-70 (phrases longues) return Math.floor(Math.random() * 30) + 70; // 70-100 (phrases très longues) } calculateCastTime(wordCount) { if (wordCount <= 4) return 1000; // 1 second if (wordCount <= 6) return 2000; // 2 seconds return 3000; // 3 seconds } init() { this.container.innerHTML = `
Wizard HP
Score: 0
Enemy HP
πŸ§™β€β™‚οΈ
Wizard Master
πŸ‘Ή
Grammar Demon
Form your spell incantation:
`; this.setupEventListeners(); this.generateNewSpells(); this.startEnemyAttackSystem(); } setupEventListeners() { document.getElementById('cast-button').addEventListener('click', () => this.castSpell()); } generateNewSpells() { this.currentSpells = []; // Get one spell of each type if (this.spells.short.length > 0) { this.currentSpells.push({ ...this.spells.short[Math.floor(Math.random() * this.spells.short.length)], type: 'short', name: 'Fireball', icon: 'πŸ”₯' }); } if (this.spells.medium.length > 0) { this.currentSpells.push({ ...this.spells.medium[Math.floor(Math.random() * this.spells.medium.length)], type: 'medium', name: 'Lightning', icon: '⚑' }); } if (this.spells.long.length > 0) { this.currentSpells.push({ ...this.spells.long[Math.floor(Math.random() * this.spells.long.length)], type: 'long', name: 'Meteor', icon: 'β˜„οΈ' }); } this.renderSpellCards(); this.selectedSpell = null; this.selectedWords = []; this.updateWordBank(); this.updateSentenceBuilder(); } renderSpellCards() { const container = document.getElementById('spell-selection'); container.innerHTML = this.currentSpells.map((spell, index) => `
${spell.icon} ${spell.name}
${spell.translation}
${spell.damage} damage
`).join(''); // Add click listeners container.querySelectorAll('.spell-card').forEach(card => { card.addEventListener('click', (e) => { const spellIndex = parseInt(e.currentTarget.dataset.spellIndex); this.selectSpell(spellIndex); }); }); } selectSpell(index) { // Remove previous selection document.querySelectorAll('.spell-card').forEach(card => card.classList.remove('selected')); // Select new spell this.selectedSpell = this.currentSpells[index]; document.querySelector(`[data-spell-index="${index}"]`).classList.add('selected'); // DΓ©marrer le timer invisible pour le bonus de vitesse this.spellStartTime = Date.now(); // Reset word selection this.selectedWords = []; this.updateWordBank(); this.updateSentenceBuilder(); } updateWordBank() { const container = document.getElementById('word-bank'); if (!this.selectedSpell) { container.innerHTML = '
Select a spell first
'; return; } // Extract the complete sentence including ALL punctuation const originalSentence = this.selectedSpell.english; const words = [...this.selectedSpell.words]; // Extract ALL punctuation from the original sentence const punctuationRegex = /[.!?,;:]/g; const punctuationMarks = originalSentence.match(punctuationRegex) || []; // Add all punctuation marks as separate word tiles with unique IDs punctuationMarks.forEach((punctuation, index) => { words.push({ word: punctuation, translation: punctuation, type: 'punctuation', pronunciation: '', uniqueId: `punct_${index}_${Date.now()}_${Math.random()}` }); }); // Shuffle the words including punctuation const shuffledWords = [...words].sort(() => Math.random() - 0.5); container.innerHTML = shuffledWords.map((wordData, index) => { const uniqueId = wordData.uniqueId || `word_${index}_${wordData.word}`; return `
${wordData.word}
`; }).join(''); // Add click listeners container.querySelectorAll('.word-tile').forEach(tile => { tile.addEventListener('click', (e) => { const word = e.currentTarget.dataset.word; const uniqueId = e.currentTarget.dataset.uniqueId; this.toggleWord(word, e.currentTarget, uniqueId); }); }); } toggleWord(word, element, uniqueId) { // Find the word by unique ID instead of just the word text const wordIndex = this.selectedWords.findIndex(selectedWord => selectedWord.uniqueId === uniqueId ); if (wordIndex > -1) { // Remove word this.selectedWords.splice(wordIndex, 1); element.classList.remove('selected'); } else { // Add word with unique ID this.selectedWords.push({ word: word, uniqueId: uniqueId }); element.classList.add('selected'); } this.updateSentenceBuilder(); this.updateCastButton(); } updateSentenceBuilder() { const container = document.getElementById('current-sentence'); const sentence = this.buildSentenceFromWords(this.selectedWords); container.textContent = sentence; } buildSentenceFromWords(words) { // Join words and handle punctuation correctly (no space before punctuation) let sentence = ''; for (let i = 0; i < words.length; i++) { const wordText = typeof words[i] === 'string' ? words[i] : words[i].word; const isPunctuation = ['.', '!', '?', ',', ';', ':'].includes(wordText); if (i === 0) { sentence = wordText; } else if (isPunctuation) { sentence += wordText; // No space before punctuation } else { sentence += ' ' + wordText; // Space before regular words } } return sentence; } updateCastButton() { const button = document.getElementById('cast-button'); // Always enable the button - let players try and fail! button.disabled = false; // Always show the same text - don't reveal if the spell is correct if (this.selectedSpell) { button.textContent = `πŸ”₯ CAST ${this.selectedSpell.name.toUpperCase()} πŸ”₯`; } else { button.textContent = 'πŸ”₯ CAST SPELL πŸ”₯'; } } castSpell() { if (!this.selectedSpell) { this.showFailEffect('noSpell'); return; } // Check if spell is correctly formed (including punctuation) const expectedSentence = this.selectedSpell.english; const playerSentence = this.buildSentenceFromWords(this.selectedWords); console.log('πŸ” Spell check:'); console.log('Expected:', expectedSentence); console.log('Player:', playerSentence); console.log('Selected words:', this.selectedWords); const isCorrect = playerSentence === expectedSentence; if (isCorrect) { // Successful cast! this.showCastingEffect(this.selectedSpell.type); // Delay the main spell effect for dramatic timing setTimeout(() => { this.showSpellEffect(this.selectedSpell.type); }, 500); // Deal damage this.enemyHP = Math.max(0, this.enemyHP - this.selectedSpell.damage); this.updateEnemyHealth(); this.showDamageNumber(this.selectedSpell.damage); // Update score - bonus multiplicateur pour phrases longues const wordCount = this.selectedWords.length; let scoreMultiplier = 10; if (wordCount >= 7) scoreMultiplier = 20; // x2 pour phrases trΓ¨s longues else if (wordCount >= 5) scoreMultiplier = 15; // x1.5 pour phrases longues // Calculer le bonus de vitesse (invisible) let speedBonus = 0; if (this.spellStartTime) { const spellTime = (Date.now() - this.spellStartTime) / 1000; // en secondes this.spellCount++; this.averageSpellTime = ((this.averageSpellTime * (this.spellCount - 1)) + spellTime) / this.spellCount; // Bonus de vitesse : plus c'est rapide, plus de points if (spellTime < 10) speedBonus = Math.floor((10 - spellTime) * 50); // Jusqu'Γ  500 bonus if (spellTime < 5) speedBonus += 300; // Bonus extra pour super rapide if (spellTime < 3) speedBonus += 500; // Bonus Γ©norme pour trΓ¨s rapide } this.score += (this.selectedSpell.damage * scoreMultiplier) + speedBonus; this.onScoreUpdate(this.score); document.getElementById('current-score').textContent = this.score; // Check win condition if (this.enemyHP <= 0) { this.handleVictory(); return; } // Generate new spells for next round setTimeout(() => { this.generateNewSpells(); // Reset timer pour nouveau round this.spellStartTime = Date.now(); }, 1000); } else { // Spell failed! Random funny effect this.showFailEffect(); } } showSpellEffect(type) { const enemyChar = document.querySelector('.enemy-character'); const rect = enemyChar.getBoundingClientRect(); // Main spell effect const effect = document.createElement('div'); effect.className = `spell-effect ${type}-effect`; effect.style.position = 'fixed'; effect.style.left = rect.left + rect.width/2 - 50 + 'px'; effect.style.top = rect.top + rect.height/2 - 50 + 'px'; document.body.appendChild(effect); // Add spell-specific enhanced effects this.createSpellParticles(type, rect); this.triggerSpellAnimation(type, enemyChar); setTimeout(() => { effect.remove(); }, 800); } createSpellParticles(type, enemyRect) { const particleCount = type === 'meteor' ? 15 : type === 'lightning' ? 12 : 8; for (let i = 0; i < particleCount; i++) { const particle = document.createElement('div'); particle.className = `spell-particle ${type}-particle`; // Random position around enemy const offsetX = (Math.random() - 0.5) * 200; const offsetY = (Math.random() - 0.5) * 200; particle.style.position = 'fixed'; particle.style.left = enemyRect.left + enemyRect.width/2 + offsetX + 'px'; particle.style.top = enemyRect.top + enemyRect.height/2 + offsetY + 'px'; particle.style.width = '6px'; particle.style.height = '6px'; particle.style.borderRadius = '50%'; particle.style.pointerEvents = 'none'; particle.style.zIndex = '1000'; // Spell-specific particle colors and animations if (type === 'fire') { particle.style.background = 'radial-gradient(circle, #ff6b7a, #ff4757)'; particle.style.animation = 'fireParticle 1.2s ease-out forwards'; particle.style.boxShadow = '0 0 10px #ff4757'; } else if (type === 'lightning') { particle.style.background = 'radial-gradient(circle, #ffd700, #ffed4e)'; particle.style.animation = 'lightningParticle 0.8s ease-out forwards'; particle.style.boxShadow = '0 0 15px #ffd700'; } else if (type === 'meteor') { particle.style.background = 'radial-gradient(circle, #a29bfe, #6c5ce7)'; particle.style.animation = 'meteorParticle 1.5s ease-out forwards'; particle.style.boxShadow = '0 0 20px #6c5ce7'; } document.body.appendChild(particle); setTimeout(() => { particle.remove(); }, 1500); } } triggerSpellAnimation(type, enemyChar) { // Screen effects based on spell type if (type === 'meteor') { // Meteor causes screen shake document.body.classList.add('screen-shake'); setTimeout(() => document.body.classList.remove('screen-shake'), 500); // Create meteor trail effect this.createMeteorTrail(); } else if (type === 'lightning') { // Lightning flash effect this.createLightningFlash(); } else if (type === 'fire') { // Fire ripple effect this.createFireRipple(enemyChar); } // Enemy hit reaction enemyChar.style.transform = 'scale(1.1)'; enemyChar.style.filter = type === 'fire' ? 'hue-rotate(30deg)' : type === 'lightning' ? 'brightness(1.5)' : 'contrast(1.3)'; setTimeout(() => { enemyChar.style.transform = ''; enemyChar.style.filter = ''; }, 300); } createMeteorTrail() { const trail = document.createElement('div'); trail.className = 'meteor-trail'; trail.style.position = 'fixed'; trail.style.top = '0'; trail.style.right = '0'; trail.style.width = '4px'; trail.style.height = '100vh'; trail.style.background = 'linear-gradient(180deg, #a29bfe, transparent)'; trail.style.animation = 'meteorTrail 0.6s ease-out forwards'; trail.style.pointerEvents = 'none'; trail.style.zIndex = '999'; document.body.appendChild(trail); setTimeout(() => trail.remove(), 600); } createLightningFlash() { const flash = document.createElement('div'); flash.style.position = 'fixed'; flash.style.top = '0'; flash.style.left = '0'; flash.style.width = '100vw'; flash.style.height = '100vh'; flash.style.background = 'rgba(255, 215, 0, 0.3)'; flash.style.animation = 'lightningFlash 0.2s ease-out'; flash.style.pointerEvents = 'none'; flash.style.zIndex = '998'; document.body.appendChild(flash); setTimeout(() => flash.remove(), 200); } createFireRipple(enemyChar) { const ripple = document.createElement('div'); const rect = enemyChar.getBoundingClientRect(); ripple.style.position = 'fixed'; ripple.style.left = rect.left + rect.width/2 - 100 + 'px'; ripple.style.top = rect.top + rect.height/2 - 100 + 'px'; ripple.style.width = '200px'; ripple.style.height = '200px'; ripple.style.border = '3px solid #ff4757'; ripple.style.borderRadius = '50%'; ripple.style.animation = 'fireRipple 0.8s ease-out forwards'; ripple.style.pointerEvents = 'none'; ripple.style.zIndex = '997'; document.body.appendChild(ripple); setTimeout(() => ripple.remove(), 800); } showCastingEffect(spellType) { const wizardChar = document.querySelector('.wizard-character'); const rect = wizardChar.getBoundingClientRect(); // Create magical circle around wizard this.createMagicCircle(rect, spellType); // Add casting sparkles this.createCastingSparkles(rect, spellType); // Wizard glow effect wizardChar.style.filter = 'drop-shadow(0 0 20px #ffd700)'; wizardChar.style.transform = 'scale(1.05)'; setTimeout(() => { wizardChar.style.filter = ''; wizardChar.style.transform = ''; }, 600); } createMagicCircle(wizardRect, spellType) { const circle = document.createElement('div'); circle.style.position = 'fixed'; circle.style.left = wizardRect.left + wizardRect.width/2 - 75 + 'px'; circle.style.top = wizardRect.top + wizardRect.height/2 - 75 + 'px'; circle.style.width = '150px'; circle.style.height = '150px'; circle.style.borderRadius = '50%'; circle.style.pointerEvents = 'none'; circle.style.zIndex = '500'; // Spell-specific circle colors if (spellType === 'fire') { circle.style.border = '3px solid #ff4757'; circle.style.boxShadow = '0 0 30px #ff4757, inset 0 0 30px rgba(255, 71, 87, 0.3)'; } else if (spellType === 'lightning') { circle.style.border = '3px solid #ffd700'; circle.style.boxShadow = '0 0 30px #ffd700, inset 0 0 30px rgba(255, 215, 0, 0.3)'; } else if (spellType === 'meteor') { circle.style.border = '3px solid #6c5ce7'; circle.style.boxShadow = '0 0 30px #6c5ce7, inset 0 0 30px rgba(108, 92, 231, 0.3)'; } circle.style.animation = 'magicCircleForm 0.6s ease-out forwards'; document.body.appendChild(circle); setTimeout(() => circle.remove(), 600); } createCastingSparkles(wizardRect, spellType) { const sparkleCount = 8; for (let i = 0; i < sparkleCount; i++) { const sparkle = document.createElement('div'); sparkle.style.position = 'fixed'; sparkle.style.width = '4px'; sparkle.style.height = '4px'; sparkle.style.borderRadius = '50%'; sparkle.style.pointerEvents = 'none'; sparkle.style.zIndex = '501'; // Position around wizard const angle = (i / sparkleCount) * 2 * Math.PI; const radius = 60; const x = wizardRect.left + wizardRect.width/2 + Math.cos(angle) * radius; const y = wizardRect.top + wizardRect.height/2 + Math.sin(angle) * radius; sparkle.style.left = x + 'px'; sparkle.style.top = y + 'px'; // Spell-specific sparkle colors if (spellType === 'fire') { sparkle.style.background = '#ff4757'; sparkle.style.boxShadow = '0 0 8px #ff4757'; } else if (spellType === 'lightning') { sparkle.style.background = '#ffd700'; sparkle.style.boxShadow = '0 0 8px #ffd700'; } else if (spellType === 'meteor') { sparkle.style.background = '#6c5ce7'; sparkle.style.boxShadow = '0 0 8px #6c5ce7'; } sparkle.style.animation = 'castingSparkle 0.6s ease-out forwards'; document.body.appendChild(sparkle); setTimeout(() => sparkle.remove(), 600); } } showDamageNumber(damage) { const damageEl = document.createElement('div'); damageEl.className = 'damage-number'; damageEl.textContent = `-${damage}`; const enemyChar = document.querySelector('.enemy-character'); const rect = enemyChar.getBoundingClientRect(); damageEl.style.position = 'fixed'; damageEl.style.left = rect.left + rect.width/2 + 'px'; damageEl.style.top = rect.top + 'px'; document.body.appendChild(damageEl); setTimeout(() => { damageEl.remove(); }, 1500); } showFailEffect(type = 'random') { const effects = ['spawnMinion', 'loseHP', 'magicQuirk', 'flyingBirds']; const selectedEffect = type === 'random' ? effects[Math.floor(Math.random() * effects.length)] : type; // Show fail message first this.showFailMessage(); // Then trigger specific effect switch(selectedEffect) { case 'spawnMinion': this.spawnMiniEnemy(); break; case 'loseHP': this.wizardTakesDamage(); break; case 'magicQuirk': this.triggerMagicQuirk(); break; case 'flyingBirds': this.summonFlyingBirds(); break; case 'noSpell': this.showFailMessage('Select a spell first! πŸͺ„'); break; } } showFailMessage(customMessage = null) { const messages = [ "Spell backfired! πŸ’₯", "Magic went wrong! πŸŒ€", "Oops! Wrong incantation! πŸ˜…", "The magic gods are not pleased! ⚑", "Your spell turned into chaos! 🎭", "Magic malfunction detected! πŸ”§" ]; const message = customMessage || messages[Math.floor(Math.random() * messages.length)]; const failEl = document.createElement('div'); failEl.className = 'fail-message'; failEl.textContent = message; document.body.appendChild(failEl); setTimeout(() => { failEl.remove(); }, 2000); } spawnMiniEnemy() { console.log('🧌 Spawning mini enemy!'); const miniEnemy = document.createElement('div'); miniEnemy.className = 'mini-enemy'; miniEnemy.textContent = 'πŸ‘Ί'; // Random position around the main enemy const mainEnemy = document.querySelector('.enemy-character'); const rect = mainEnemy.getBoundingClientRect(); miniEnemy.style.position = 'fixed'; miniEnemy.style.left = (rect.left + Math.random() * 200 - 100) + 'px'; miniEnemy.style.top = (rect.top + Math.random() * 200 - 100) + 'px'; document.body.appendChild(miniEnemy); // Mini enemy disappears after 5 seconds setTimeout(() => { miniEnemy.remove(); }, 5000); // Make main enemy slightly stronger this.enemyHP = Math.min(100, this.enemyHP + 5); this.updateEnemyHealth(); } wizardTakesDamage() { console.log('πŸ”₯ Wizard takes damage!'); this.playerHP = Math.max(0, this.playerHP - 10); document.getElementById('player-health').style.width = this.playerHP + '%'; // Screen shake effect document.body.classList.add('screen-shake'); setTimeout(() => { document.body.classList.remove('screen-shake'); }, 500); // Show damage on wizard const damageEl = document.createElement('div'); damageEl.className = 'damage-number'; damageEl.textContent = '-10'; damageEl.style.color = '#ff4757'; const wizardChar = document.querySelector('.wizard-character'); const rect = wizardChar.getBoundingClientRect(); damageEl.style.position = 'fixed'; damageEl.style.left = rect.left + rect.width/2 + 'px'; damageEl.style.top = rect.top + 'px'; document.body.appendChild(damageEl); setTimeout(() => { damageEl.remove(); }, 1500); // Check if wizard dies if (this.playerHP <= 0) { setTimeout(() => { this.handleDefeat(); }, 1000); } } triggerMagicQuirk() { console.log('πŸŒ€ Magic quirk activated!'); // Create multiple quirks at random positions const numQuirks = 2 + Math.floor(Math.random() * 2); // 2-3 quirks for (let i = 0; i < numQuirks; i++) { setTimeout(() => { const quirk = document.createElement('div'); quirk.className = 'magic-quirk'; // Random position within viewport const x = 20 + Math.random() * 60; // 20% to 80% of viewport width const y = 20 + Math.random() * 60; // 20% to 80% of viewport height quirk.style.left = x + '%'; quirk.style.top = y + '%'; quirk.style.transform = 'translate(-50%, -50%)'; document.body.appendChild(quirk); setTimeout(() => { quirk.remove(); }, 2000); }, i * 300); // Stagger the quirks } // Scramble the word bank for extra chaos setTimeout(() => { this.updateWordBank(); }, 1000); } summonFlyingBirds() { console.log('🐦 Summoning flying birds!'); const birds = ['🐦', 'πŸ•ŠοΈ', 'πŸ¦…', '🦜', '🐧', 'πŸ¦†', '🦒', 'πŸ“', 'πŸ¦ƒ', '🦚', '🐀', '🐣', 'πŸ₯']; const paths = ['bird-path-1', 'bird-path-2', 'bird-path-3', 'bird-path-4', 'bird-path-5']; const numBirds = 5 + Math.floor(Math.random() * 3); // 5-7 birds maintenant ! for (let i = 0; i < numBirds; i++) { setTimeout(() => { const bird = document.createElement('div'); const pathClass = paths[i % paths.length]; bird.className = `flying-bird ${pathClass}`; bird.textContent = birds[Math.floor(Math.random() * birds.length)]; document.body.appendChild(bird); console.log(`🐦 Bird ${i+1} spawned with class: ${bird.className}`); setTimeout(() => { bird.remove(); }, 30000); }, i * 500); // Stagger bird appearances } } updateEnemyHealth() { const healthBar = document.getElementById('enemy-health'); const percentage = (this.enemyHP / 100) * 100; healthBar.style.width = percentage + '%'; } getRandomAttackTime() { // Enemy attacks every 8-15 seconds randomly return 8000 + Math.random() * 7000; } startEnemyAttackSystem() { this.scheduleNextEnemyAttack(); } scheduleNextEnemyAttack() { this.enemyAttackTimer = setTimeout(() => { this.executeEnemyAttack(); this.scheduleNextEnemyAttack(); // Schedule next attack }, this.nextEnemyAttack); } executeEnemyAttack() { console.log('πŸ‘Ή Enemy is attacking!'); const enemyChar = document.querySelector('.enemy-character'); // Show attack warning this.showEnemyAttackWarning(); // Enemy charging animation enemyChar.classList.add('enemy-charging'); // Attack after 2 seconds warning setTimeout(() => { enemyChar.classList.remove('enemy-charging'); this.dealEnemyDamage(); this.showEnemyAttackEffect(); }, 2000); // Set next attack time this.nextEnemyAttack = this.getRandomAttackTime(); } showEnemyAttackWarning() { const enemyChar = document.querySelector('.enemy-character'); // Remove existing warning const existingWarning = enemyChar.querySelector('.enemy-attack-warning'); if (existingWarning) { existingWarning.remove(); } const warning = document.createElement('div'); warning.className = 'enemy-attack-warning'; warning.textContent = '⚠️ INCOMING ATTACK!'; enemyChar.style.position = 'relative'; enemyChar.appendChild(warning); // Remove warning after attack setTimeout(() => { warning.remove(); }, 2000); } dealEnemyDamage() { const damage = 12 + Math.floor(Math.random() * 8); // 12-20 damage this.playerHP = Math.max(0, this.playerHP - damage); document.getElementById('player-health').style.width = this.playerHP + '%'; // Screen shake document.body.classList.add('screen-shake'); setTimeout(() => { document.body.classList.remove('screen-shake'); }, 500); // Show damage number on wizard const damageEl = document.createElement('div'); damageEl.className = 'damage-number'; damageEl.textContent = `-${damage}`; damageEl.style.color = '#ff4757'; const wizardChar = document.querySelector('.wizard-character'); const rect = wizardChar.getBoundingClientRect(); damageEl.style.position = 'fixed'; damageEl.style.left = rect.left + rect.width/2 + 'px'; damageEl.style.top = rect.top + 'px'; document.body.appendChild(damageEl); setTimeout(() => { damageEl.remove(); }, 1500); console.log(`πŸ’” Player took ${damage} damage! HP: ${this.playerHP}`); // Check if player dies if (this.playerHP <= 0) { setTimeout(() => { this.handleDefeat(); }, 1000); } } showEnemyAttackEffect() { const effect = document.createElement('div'); effect.className = 'enemy-attack-effect'; const wizardChar = document.querySelector('.wizard-character'); const rect = wizardChar.getBoundingClientRect(); effect.style.position = 'fixed'; effect.style.left = rect.left + rect.width/2 - 75 + 'px'; effect.style.top = rect.top + rect.height/2 - 75 + 'px'; document.body.appendChild(effect); setTimeout(() => { effect.remove(); }, 1000); } handleVictory() { clearTimeout(this.enemyAttackTimer); const bonusScore = 1000; // Fixed victory bonus this.score += bonusScore; this.container.innerHTML += `
πŸŽ‰ VICTORY! πŸŽ‰
You defeated the Grammar Demon!
Final Score: ${this.score}
Time Bonus: +${bonusScore}
`; this.onGameEnd(this.score); } handleDefeat() { clearTimeout(this.enemyAttackTimer); this.container.innerHTML += `
πŸ’€ DEFEATED πŸ’€
The Grammar Demon proved too strong!
Final Score: ${this.score}
`; this.onGameEnd(this.score); } start() { // Game starts immediately when initialized } destroy() { if (this.enemyAttackTimer) { clearTimeout(this.enemyAttackTimer); } const styleSheet = document.getElementById('wizard-spell-caster-styles'); if (styleSheet) { styleSheet.remove(); } } restart() { this.destroy(); this.score = 0; this.enemyHP = 100; this.playerHP = 100; this.spellStartTime = Date.now(); this.averageSpellTime = 0; this.spellCount = 0; this.nextEnemyAttack = this.getRandomAttackTime(); this.init(); } } // Register the game module window.GameModules = window.GameModules || {}; window.GameModules.WizardSpellCaster = WizardSpellCaster;