Spaces:
Running
Running
| <html lang="de"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Noras Wortratespiel</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .hangman-part { | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| } | |
| .hangman-part.visible { | |
| opacity: 1; | |
| } | |
| .letter-tile { | |
| transition: all 0.3s ease; | |
| } | |
| .letter-tile:hover:not(.guessed) { | |
| transform: scale(1.1); | |
| box-shadow: 0 0 10px rgba(59, 130, 246, 0.5); | |
| } | |
| @keyframes bounce { | |
| 0%, 100% { transform: translateY(0); } | |
| 50% { transform: translateY(-10px); } | |
| } | |
| .bounce { | |
| animation: bounce 0.5s; | |
| } | |
| .confetti { | |
| position: absolute; | |
| width: 10px; | |
| height: 10px; | |
| background-color: #f00; | |
| opacity: 0; | |
| } | |
| .hangman-face { | |
| transition: all 0.3s ease; | |
| } | |
| .happy-face { | |
| transform: rotate(0deg); | |
| } | |
| .sad-face { | |
| transform: rotate(180deg); | |
| } | |
| .final-stats { | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| border-radius: 1rem; | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
| } | |
| .progress-ring { | |
| transition: stroke-dashoffset 0.5s; | |
| transform: rotate(-90deg); | |
| transform-origin: 50% 50%; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-blue-50 min-h-screen flex flex-col items-center font-sans"> | |
| <header class="w-full bg-blue-600 text-white py-4 shadow-md"> | |
| <div class="container mx-auto px-4"> | |
| <h1 class="text-3xl font-bold text-center">Noras Wortratespiel</h1> | |
| </div> | |
| </header> | |
| <main class="container mx-auto px-4 py-8 flex-grow flex flex-col items-center"> | |
| <div id="game-container" class="w-full max-w-4xl bg-white rounded-xl shadow-lg p-6 mb-8"> | |
| <div class="flex flex-col md:flex-row gap-8"> | |
| <!-- Hangman Drawing --> | |
| <div class="w-full md:w-1/2 flex flex-col items-center"> | |
| <div class="relative w-64 h-64 mb-6"> | |
| <!-- Gallows --> | |
| <div class="absolute top-0 left-1/2 transform -translate-x-1/2 w-1 h-10 bg-gray-800 rounded"></div> | |
| <div class="absolute top-0 left-1/4 w-1/2 h-1 bg-gray-800 rounded"></div> | |
| <div class="absolute top-0 left-1/4 w-1 h-64 bg-gray-800 rounded"></div> | |
| <div class="absolute bottom-0 left-0 w-40 h-1 bg-gray-800 rounded"></div> | |
| <!-- Hangman Parts --> | |
| <div id="head" class="hangman-part absolute top-10 left-1/2 transform -translate-x-1/2 w-12 h-12 rounded-full border-4 border-gray-800"> | |
| <div id="face" class="hangman-face happy-face absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-8 h-4"> | |
| <div class="absolute top-0 left-0 w-2 h-2 bg-gray-800 rounded-full"></div> | |
| <div class="absolute top-0 right-0 w-2 h-2 bg-gray-800 rounded-full"></div> | |
| <div class="absolute bottom-0 left-1/2 transform -translate-x-1/2 w-4 h-1 bg-gray-800 rounded-full"></div> | |
| </div> | |
| </div> | |
| <div id="body" class="hangman-part absolute top-22 left-1/2 transform -translate-x-1/2 w-1 h-16 bg-gray-800 rounded"></div> | |
| <div id="left-arm" class="hangman-part absolute top-24 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-1 h-10 bg-gray-800 rounded origin-top rotate-45"></div> | |
| <div id="right-arm" class="hangman-part absolute top-24 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-1 h-10 bg-gray-800 rounded origin-top -rotate-45"></div> | |
| <div id="left-leg" class="hangman-part absolute top-38 left-1/2 transform -translate-x-1/2 w-1 h-10 bg-gray-800 rounded origin-top rotate-30"></div> | |
| <div id="right-leg" class="hangman-part absolute top-38 left-1/2 transform -translate-x-1/2 w-1 h-10 bg-gray-800 rounded origin-top -rotate-30"></div> | |
| </div> | |
| <div class="text-center"> | |
| <p class="text-lg font-semibold mb-2">Fehler: <span id="mistakes">0</span>/8</p> | |
| <div class="w-full bg-gray-200 rounded-full h-4"> | |
| <div id="progress-bar" class="bg-green-500 h-4 rounded-full" style="width: 100%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Game Area --> | |
| <div class="w-full md:w-1/2 flex flex-col"> | |
| <div class="mb-6"> | |
| <p class="text-gray-600 mb-2">Kategorie: <span id="category" class="font-semibold">Tiere</span></p> | |
| <p class="text-gray-600 mb-2">Wörter übrig: <span id="words-left" class="font-semibold">10</span></p> | |
| <div id="word-display" class="flex justify-center gap-2 mb-6 flex-wrap"></div> | |
| </div> | |
| <div class="mb-6"> | |
| <p class="text-gray-600 mb-2">Verfügbare Buchstaben:</p> | |
| <div id="keyboard" class="flex flex-wrap gap-2 justify-center"></div> | |
| </div> | |
| <div id="hint-area" class="mb-4 p-3 bg-yellow-50 rounded-lg border border-yellow-200 hidden"> | |
| <p class="text-yellow-800 font-medium"><i class="fas fa-lightbulb mr-2"></i>Tipp: <span id="hint-text"></span></p> | |
| </div> | |
| <button id="hint-button" class="mb-6 bg-yellow-400 hover:bg-yellow-500 text-yellow-800 font-medium py-2 px-4 rounded-lg transition-colors"> | |
| <i class="fas fa-lightbulb mr-2"></i>Tipp anzeigen | |
| </button> | |
| <div id="message" class="hidden mb-4 p-3 rounded-lg text-center font-bold"></div> | |
| <button id="new-game-button" class="mt-auto bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition-colors hidden"> | |
| Nächstes Wort | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Game History --> | |
| <div id="game-history" class="w-full max-w-4xl bg-white rounded-xl shadow-lg p-6 mb-8 hidden"> | |
| <h2 class="text-xl font-bold mb-4 text-center">Deine Spiele</h2> | |
| <div class="overflow-x-auto"> | |
| <table class="w-full"> | |
| <thead> | |
| <tr class="bg-gray-100"> | |
| <th class="px-4 py-2 text-left">Datum</th> | |
| <th class="px-4 py-2 text-left">Wort</th> | |
| <th class="px-4 py-2 text-left">Ergebnis</th> | |
| <th class="px-4 py-2 text-left">Fehler</th> | |
| </tr> | |
| </thead> | |
| <tbody id="history-table"> | |
| <!-- Filled by JavaScript --> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="mt-4 flex justify-between items-center"> | |
| <div> | |
| <p class="font-semibold">Statistik:</p> | |
| <p>Gewonnen: <span id="wins">0</span> | Verloren: <span id="losses">0</span></p> | |
| </div> | |
| <button id="toggle-history" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-4 rounded-lg transition-colors"> | |
| <i class="fas fa-eye-slash mr-2"></i>Verstecken | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Final Statistics --> | |
| <div id="final-stats" class="w-full max-w-4xl final-stats p-8 mb-8 hidden"> | |
| <h2 class="text-3xl font-bold text-center mb-6">Spiel beendet! 🎉</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8 items-center"> | |
| <div> | |
| <h3 class="text-xl font-semibold mb-4">Zusammenfassung</h3> | |
| <div class="space-y-4"> | |
| <div class="flex justify-between items-center"> | |
| <span class="text-gray-700">Gewonnene Spiele:</span> | |
| <span id="final-wins" class="font-bold text-green-600">0</span> | |
| </div> | |
| <div class="flex justify-between items-center"> | |
| <span class="text-gray-700">Verlorene Spiele:</span> | |
| <span id="final-losses" class="font-bold text-red-600">0</span> | |
| </div> | |
| <div class="flex justify-between items-center"> | |
| <span class="text-gray-700">Wörter erraten:</span> | |
| <span id="words-guessed" class="font-bold text-blue-600">0/0</span> | |
| </div> | |
| <div class="flex justify-between items-center"> | |
| <span class="text-gray-700">Durchschnittliche Fehler:</span> | |
| <span id="avg-mistakes" class="font-bold text-purple-600">0</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex flex-col items-center"> | |
| <svg width="200" height="200" viewBox="0 0 200 200" class="mb-4"> | |
| <circle cx="100" cy="100" r="90" fill="none" stroke="#e5e7eb" stroke-width="10"/> | |
| <circle id="win-ring" cx="100" cy="100" r="90" fill="none" stroke="#10b981" stroke-width="10" stroke-dasharray="565.48" stroke-dashoffset="565.48" class="progress-ring"/> | |
| </svg> | |
| <p class="text-center text-gray-700">Erfolgsquote: <span id="success-rate" class="font-bold">0</span>%</p> | |
| </div> | |
| </div> | |
| <div class="mt-8 text-center"> | |
| <button id="restart-game-button" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition-colors"> | |
| <i class="fas fa-redo mr-2"></i>Neues Spiel starten | |
| </button> | |
| </div> | |
| </div> | |
| <div class="w-full max-w-4xl bg-white rounded-xl shadow-lg p-6"> | |
| <h2 class="text-xl font-bold mb-4 text-center">Wie spielt man?</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div class="bg-blue-100 p-4 rounded-lg"> | |
| <div class="flex items-center mb-2"> | |
| <div class="bg-blue-600 text-white rounded-full w-8 h-8 flex items-center justify-center mr-3">1</div> | |
| <h3 class="font-semibold">Wort erraten</h3> | |
| </div> | |
| <p class="text-gray-700">Klicke auf die Buchstaben, um das geheime Wort zu erraten.</p> | |
| </div> | |
| <div class="bg-green-100 p-4 rounded-lg"> | |
| <div class="flex items-center mb-2"> | |
| <div class="bg-green-600 text-white rounded-full w-8 h-8 flex items-center justify-center mr-3">2</div> | |
| <h3 class="font-semibold">Achtung Fehler</h3> | |
| </div> | |
| <p class="text-gray-700">Jeder falsche Buchstabe bringt den Strichmännchen näher zum Galgen.</p> | |
| </div> | |
| <div class="bg-yellow-100 p-4 rounded-lg"> | |
| <div class="flex items-center mb-2"> | |
| <div class="bg-yellow-600 text-white rounded-full w-8 h-8 flex items-center justify-center mr-3">3</div> | |
| <h3 class="font-semibold">Tipps nutzen</h3> | |
| </div> | |
| <p class="text-gray-700">Wenn du nicht weiter weißt, klicke auf den Tipp-Button für Hilfe.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <footer class="w-full bg-blue-600 text-white py-4 mt-8"> | |
| <div class="container mx-auto px-4 text-center"> | |
| <p>Viel Spaß beim Spielen, liebe Nora! ❤️</p> | |
| </div> | |
| </footer> | |
| <audio id="correct-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-correct-answer-tone-2870.mp3" preload="auto"></audio> | |
| <audio id="wrong-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-wrong-answer-fail-notification-946.mp3" preload="auto"></audio> | |
| <audio id="win-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-achievement-bell-600.mp3" preload="auto"></audio> | |
| <audio id="lose-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-sad-game-over-675.mp3" preload="auto"></audio> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Game words with categories and hints (easy German words) | |
| const wordCategories = { | |
| tiere: { | |
| name: "Tiere", | |
| words: [ | |
| { word: "HUND", hint: "Bellt und ist der beste Freund des Menschen" }, | |
| { word: "KATZE", hint: "Schnurrt und hat weiche Pfoten" }, | |
| { word: "FISCH", hint: "Schwimmt im Wasser und atmet durch Kiemen" }, | |
| { word: "VOGEL", hint: "Hat Federn und kann fliegen" }, | |
| { word: "MAUS", hint: "Klein, schnell und mag Käse" }, | |
| { word: "PFERD", hint: "Groß, hat eine Mähne und man kann darauf reiten" }, | |
| { word: "KUH", hint: "Gibt Milch und sagt 'Muh'" }, | |
| { word: "HASE", hint: "Hat lange Ohren und hoppelt" }, | |
| { word: "ENTE", hint: "Schwimmt auf dem Teich und sagt 'Quak'" }, | |
| { word: "SCHWEIN", hint: "Ist rosa und wühlt im Schlamm" } | |
| ] | |
| }, | |
| schule: { | |
| name: "Schule", | |
| words: [ | |
| { word: "BUCH", hint: "Hat viele Seiten mit Geschichten oder Wissen" }, | |
| { word: "STIFT", hint: "Damit schreibt oder malt man" }, | |
| { word: "TASCHE", hint: "Trägt man auf dem Rücken mit Schulmaterial" }, | |
| { word: "LEHRER", hint: "Bringt Kindern etwas bei" }, | |
| { word: "KREIDE", hint: "Damit schreibt man an die Tafel" }, | |
| { word: "LINEAL", hint: "Gerades Werkzeug zum Zeichnen von Linien" }, | |
| { word: "RADIERER", hint: "Damit entfernt man Bleistiftstriche" }, | |
| { word: "HEFT", hint: "Hat leere Seiten zum Schreiben" }, | |
| { word: "FARBE", hint: "Damit malt man bunte Bilder" }, | |
| { word: "SCHERE", hint: "Damit schneidet man Papier" } | |
| ] | |
| }, | |
| spielzeug: { | |
| name: "Spielzeug", | |
| words: [ | |
| { word: "BALL", hint: "Rund und man kann damit werfen oder kicken" }, | |
| { word: "PUPPE", hint: "Sieht aus wie ein Baby oder eine Person" }, | |
| { word: "AUTO", hint: "Hat Räder und man kann es schieben" }, | |
| { word: "BAUER", hint: "Damit kann man Türme und Häuser bauen" }, | |
| { word: "PUZZLE", hint: "Viele Teile, die zusammengesetzt werden müssen" }, | |
| { word: "SEIL", hint: "Langes Ding zum Springen oder Klettern" }, | |
| { word: "SPIELKARTEN", hint: "Damit kann man viele verschiedene Spiele spielen" }, | |
| { word: "TEDDY", hint: "Weicher Bär zum Kuscheln" }, | |
| { word: "SPIELUHR", hint: "Spielt Musik wenn man sie aufzieht" }, | |
| { word: "KNETE", hint: "Weiches Material zum Formen von Figuren" } | |
| ] | |
| }, | |
| essen: { | |
| name: "Essen", | |
| words: [ | |
| { word: "APFEL", hint: "Rund, rot oder grün und sehr gesund" }, | |
| { word: "BANANE", hint: "Länglich, gelb und kommt von Bäumen" }, | |
| { word: "BROT", hint: "Wird aus Mehl gebacken und ist meistens braun" }, | |
| { word: "KAESE", hint: "Kommt von der Kuh und ist oft gelb" }, | |
| { word: "EI", hint: "Kommt von Hühnern und hat eine harte Schale" }, | |
| { word: "SUPPE", hint: "Flüssiges Essen, das man löffelt" }, | |
| { word: "NUDELN", hint: "Lange, dünne Teigwaren aus Italien" }, | |
| { word: "EIS", hint: "Kalt, süß und schmilzt in der Sonne" }, | |
| { word: "KEKS", hint: "Klein, knusprig und oft mit Schokolade" }, | |
| { word: "MARMELADE", hint: "Süßer Brotaufstrich aus Früchten" } | |
| ] | |
| }, | |
| familie: { | |
| name: "Familie", | |
| words: [ | |
| { word: "MAMA", hint: "Die wichtigste Frau in deinem Leben" }, | |
| { word: "PAPA", hint: "Der wichtigste Mann in deinem Leben" }, | |
| { word: "OMA", hint: "Die Mutter deiner Mutter oder deines Vaters" }, | |
| { word: "OPA", hint: "Der Vater deiner Mutter oder deines Vaters" }, | |
| { word: "BRUDER", hint: "Ein männliches Geschwisterkind" }, | |
| { word: "SCHWESTER", hint: "Ein weibliches Geschwisterkind" }, | |
| { word: "TANTE", hint: "Die Schwester deiner Mutter oder deines Vaters" }, | |
| { word: "ONKEL", hint: "Der Bruder deiner Mutter oder deines Vaters" }, | |
| { word: "COUSIN", hint: "Der Sohn deiner Tante oder deines Onkels" }, | |
| { word: "COUSINE", hint: "Die Tochter deiner Tante oder deines Onkels" } | |
| ] | |
| }, | |
| natur: { | |
| name: "Natur", | |
| words: [ | |
| { word: "BAUM", hint: "Große Pflanze mit Stamm und Blättern" }, | |
| { word: "BLUME", hint: "Schöne Pflanze mit bunten Blüten" }, | |
| { word: "GRAS", hint: "Grüne Pflanze, die auf Wiesen wächst" }, | |
| { word: "SONNE", hint: "Großer, heißer Stern am Himmel" }, | |
| { word: "WOLKE", hint: "Weiß und flauschig am Himmel" }, | |
| { word: "REGEN", hint: "Wasser, das vom Himmel fällt" }, | |
| { word: "SCHNEE", hint: "Weiß, kalt und fällt im Winter" }, | |
| { word: "BERG", hint: "Sehr hoch und man kann ihn besteigen" }, | |
| { word: "FLUSS", hint: "Langes, fließendes Gewässer" }, | |
| { word: "MEER", hint: "Sehr großes Salzwasser" } | |
| ] | |
| } | |
| }; | |
| // Game state | |
| let currentWord = ""; | |
| let guessedLetters = []; | |
| let mistakes = 0; | |
| let maxMistakes = 8; | |
| let hintUsed = false; | |
| let currentCategory = ""; | |
| let gameActive = true; | |
| let gameHistory = JSON.parse(localStorage.getItem('norasGameHistory')) || []; | |
| let wins = 0; | |
| let losses = 0; | |
| let usedWords = {}; | |
| let wordsLeft = 0; | |
| // Initialize usedWords object | |
| function initUsedWords() { | |
| usedWords = {}; | |
| for (const category in wordCategories) { | |
| usedWords[category] = []; | |
| } | |
| } | |
| // DOM elements | |
| const wordDisplay = document.getElementById('word-display'); | |
| const keyboard = document.getElementById('keyboard'); | |
| const mistakesDisplay = document.getElementById('mistakes'); | |
| const progressBar = document.getElementById('progress-bar'); | |
| const message = document.getElementById('message'); | |
| const newGameButton = document.getElementById('new-game-button'); | |
| const hintButton = document.getElementById('hint-button'); | |
| const hintArea = document.getElementById('hint-area'); | |
| const hintText = document.getElementById('hint-text'); | |
| const categoryDisplay = document.getElementById('category'); | |
| const gameHistorySection = document.getElementById('game-history'); | |
| const historyTable = document.getElementById('history-table'); | |
| const winsDisplay = document.getElementById('wins'); | |
| const lossesDisplay = document.getElementById('losses'); | |
| const toggleHistoryButton = document.getElementById('toggle-history'); | |
| const hangmanParts = [ | |
| 'head', 'body', 'left-arm', 'right-arm', | |
| 'left-leg', 'right-leg' | |
| ]; | |
| const face = document.getElementById('face'); | |
| const correctSound = document.getElementById('correct-sound'); | |
| const wrongSound = document.getElementById('wrong-sound'); | |
| const winSound = document.getElementById('win-sound'); | |
| const loseSound = document.getElementById('lose-sound'); | |
| const wordsLeftDisplay = document.getElementById('words-left'); | |
| const finalStatsSection = document.getElementById('final-stats'); | |
| const finalWinsDisplay = document.getElementById('final-wins'); | |
| const finalLossesDisplay = document.getElementById('final-losses'); | |
| const wordsGuessedDisplay = document.getElementById('words-guessed'); | |
| const avgMistakesDisplay = document.getElementById('avg-mistakes'); | |
| const successRateDisplay = document.getElementById('success-rate'); | |
| const restartGameButton = document.getElementById('restart-game-button'); | |
| const winRing = document.getElementById('win-ring'); | |
| const gameContainer = document.getElementById('game-container'); | |
| // Initialize the game | |
| function initGame() { | |
| // Reset game state | |
| guessedLetters = []; | |
| mistakes = 0; | |
| hintUsed = false; | |
| gameActive = true; | |
| // Clear displays | |
| wordDisplay.innerHTML = ''; | |
| keyboard.innerHTML = ''; | |
| message.classList.add('hidden'); | |
| newGameButton.classList.add('hidden'); | |
| hintArea.classList.add('hidden'); | |
| hintButton.classList.remove('hidden'); | |
| // Reset hangman | |
| hangmanParts.forEach(part => { | |
| document.getElementById(part).classList.remove('visible'); | |
| }); | |
| // Reset face | |
| face.classList.remove('sad-face'); | |
| face.classList.add('happy-face'); | |
| // Select a random category that still has words left | |
| const availableCategories = Object.keys(wordCategories).filter(category => { | |
| return wordCategories[category].words.length > usedWords[category].length; | |
| }); | |
| // If no categories have words left, show final stats | |
| if (availableCategories.length === 0) { | |
| showFinalStats(); | |
| return; | |
| } | |
| currentCategory = availableCategories[Math.floor(Math.random() * availableCategories.length)]; | |
| categoryDisplay.textContent = wordCategories[currentCategory].name; | |
| // Select a random word from the category that hasn't been used yet | |
| const availableWords = wordCategories[currentCategory].words.filter(wordObj => { | |
| return !usedWords[currentCategory].includes(wordObj.word); | |
| }); | |
| const randomWordObj = availableWords[Math.floor(Math.random() * availableWords.length)]; | |
| currentWord = randomWordObj.word; | |
| hintText.textContent = randomWordObj.hint; | |
| // Update words left display | |
| wordsLeft = wordCategories[currentCategory].words.length - usedWords[currentCategory].length - 1; | |
| wordsLeftDisplay.textContent = wordsLeft; | |
| // Update UI | |
| updateMistakesDisplay(); | |
| createWordDisplay(); | |
| createKeyboard(); | |
| // Update game history display | |
| updateGameHistory(); | |
| } | |
| // Create the word display with blanks | |
| function createWordDisplay() { | |
| wordDisplay.innerHTML = ''; | |
| for (let i = 0; i < currentWord.length; i++) { | |
| const letter = currentWord[i]; | |
| const letterElement = document.createElement('div'); | |
| letterElement.className = 'w-10 h-10 flex items-center justify-center text-2xl font-bold border-b-2 border-gray-800'; | |
| if (guessedLetters.includes(letter)) { | |
| letterElement.textContent = letter; | |
| letterElement.classList.add('text-blue-600'); | |
| } else { | |
| letterElement.textContent = ' '; | |
| } | |
| wordDisplay.appendChild(letterElement); | |
| } | |
| } | |
| // Create the keyboard | |
| function createKeyboard() { | |
| const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜ'.split(''); | |
| letters.forEach(letter => { | |
| const button = document.createElement('button'); | |
| button.textContent = letter; | |
| button.className = 'letter-tile w-10 h-10 flex items-center justify-center bg-blue-100 hover:bg-blue-200 text-blue-800 font-bold rounded-lg transition-all'; | |
| if (guessedLetters.includes(letter)) { | |
| button.classList.add('guessed'); | |
| button.classList.remove('bg-blue-100', 'hover:bg-blue-200'); | |
| button.classList.add('bg-gray-200', 'cursor-not-allowed'); | |
| } | |
| button.addEventListener('click', () => handleLetterClick(letter, button)); | |
| keyboard.appendChild(button); | |
| }); | |
| } | |
| // Handle letter clicks | |
| function handleLetterClick(letter, button) { | |
| if (!gameActive || guessedLetters.includes(letter)) return; | |
| guessedLetters.push(letter); | |
| button.classList.add('guessed'); | |
| button.classList.remove('bg-blue-100', 'hover:bg-blue-200'); | |
| button.classList.add('bg-gray-200', 'cursor-not-allowed'); | |
| if (currentWord.includes(letter)) { | |
| // Correct guess | |
| button.classList.remove('bg-gray-200'); | |
| button.classList.add('bg-green-100', 'text-green-800'); | |
| button.classList.add('bounce'); | |
| setTimeout(() => button.classList.remove('bounce'), 500); | |
| // Play correct sound | |
| correctSound.currentTime = 0; | |
| correctSound.play(); | |
| createWordDisplay(); | |
| checkWin(); | |
| } else { | |
| // Wrong guess | |
| button.classList.remove('bg-gray-200'); | |
| button.classList.add('bg-red-100', 'text-red-800'); | |
| button.classList.add('bounce'); | |
| setTimeout(() => button.classList.remove('bounce'), 500); | |
| // Play wrong sound | |
| wrongSound.currentTime = 0; | |
| wrongSound.play(); | |
| mistakes++; | |
| updateMistakesDisplay(); | |
| showHangmanPart(); | |
| checkLose(); | |
| } | |
| } | |
| // Show the next hangman part | |
| function showHangmanPart() { | |
| if (mistakes <= hangmanParts.length) { | |
| const part = hangmanParts[mistakes - 1]; | |
| document.getElementById(part).classList.add('visible'); | |
| // Make face sad when mistakes reach half | |
| if (mistakes >= maxMistakes / 2) { | |
| face.classList.remove('happy-face'); | |
| face.classList.add('sad-face'); | |
| } | |
| } | |
| } | |
| // Update mistakes display | |
| function updateMistakesDisplay() { | |
| mistakesDisplay.textContent = mistakes; | |
| const progressPercentage = 100 - (mistakes / maxMistakes * 100); | |
| progressBar.style.width = `${progressPercentage}%`; | |
| if (mistakes >= 5) { | |
| progressBar.classList.remove('bg-green-500'); | |
| progressBar.classList.add('bg-yellow-500'); | |
| } | |
| if (mistakes >= 7) { | |
| progressBar.classList.remove('bg-yellow-500'); | |
| progressBar.classList.add('bg-red-500'); | |
| } | |
| } | |
| // Check if player won | |
| function checkWin() { | |
| const wordGuessed = currentWord.split('').every(letter => guessedLetters.includes(letter)); | |
| if (wordGuessed) { | |
| gameActive = false; | |
| message.textContent = 'Herzlichen Glückwunsch! Du hast gewonnen! 🎉'; | |
| message.classList.remove('hidden'); | |
| message.classList.add('bg-green-100', 'text-green-800'); | |
| newGameButton.classList.remove('hidden'); | |
| // Play win sound | |
| winSound.currentTime = 0; | |
| winSound.play(); | |
| // Mark word as used | |
| usedWords[currentCategory].push(currentWord); | |
| // Add to game history | |
| addToGameHistory(true); | |
| createConfetti(); | |
| } | |
| } | |
| // Check if player lost | |
| function checkLose() { | |
| if (mistakes >= maxMistakes) { | |
| gameActive = false; | |
| message.textContent = `Schade! Das Wort war: ${currentWord}`; | |
| message.classList.remove('hidden'); | |
| message.classList.add('bg-red-100', 'text-red-800'); | |
| newGameButton.classList.remove('hidden'); | |
| // Play lose sound | |
| loseSound.currentTime = 0; | |
| loseSound.play(); | |
| // Mark word as used (only if not already marked) | |
| if (!usedWords[currentCategory].includes(currentWord)) { | |
| usedWords[currentCategory].push(currentWord); | |
| } | |
| // Add to game history | |
| addToGameHistory(false); | |
| // Reveal the word | |
| revealWord(); | |
| } | |
| } | |
| // Reveal the word when game is lost | |
| function revealWord() { | |
| wordDisplay.innerHTML = ''; | |
| for (let i = 0; i < currentWord.length; i++) { | |
| const letter = currentWord[i]; | |
| const letterElement = document.createElement('div'); | |
| letterElement.className = 'w-10 h-10 flex items-center justify-center text-2xl font-bold border-b-2 border-gray-800'; | |
| letterElement.textContent = letter; | |
| if (guessedLetters.includes(letter)) { | |
| letterElement.classList.add('text-blue-600'); | |
| } else { | |
| letterElement.classList.add('text-red-600'); | |
| } | |
| wordDisplay.appendChild(letterElement); | |
| } | |
| } | |
| // Add game result to history | |
| function addToGameHistory(won) { | |
| const gameResult = { | |
| date: new Date().toLocaleString(), | |
| word: currentWord, | |
| result: won ? 'Gewonnen' : 'Verloren', | |
| mistakes: mistakes, | |
| won: won | |
| }; | |
| gameHistory.unshift(gameResult); | |
| // Keep only last 20 games | |
| if (gameHistory.length > 20) { | |
| gameHistory = gameHistory.slice(0, 20); | |
| } | |
| // Save to localStorage | |
| localStorage.setItem('norasGameHistory', JSON.stringify(gameHistory)); | |
| localStorage.setItem('norasUsedWords', JSON.stringify(usedWords)); | |
| // Update stats | |
| updateStats(); | |
| } | |
| // Update game history display | |
| function updateGameHistory() { | |
| if (gameHistory.length > 0) { | |
| gameHistorySection.classList.remove('hidden'); | |
| historyTable.innerHTML = ''; | |
| gameHistory.forEach(game => { | |
| const row = document.createElement('tr'); | |
| row.className = 'border-b border-gray-200 hover:bg-gray-50'; | |
| row.innerHTML = ` | |
| <td class="px-4 py-2">${game.date}</td> | |
| <td class="px-4 py-2">${game.word}</td> | |
| <td class="px-4 py-2 ${game.won ? 'text-green-600' : 'text-red-600'}">${game.result}</td> | |
| <td class="px-4 py-2">${game.mistakes}</td> | |
| `; | |
| historyTable.appendChild(row); | |
| }); | |
| } else { | |
| gameHistorySection.classList.add('hidden'); | |
| } | |
| } | |
| // Update win/loss stats | |
| function updateStats() { | |
| wins = gameHistory.filter(game => game.won).length; | |
| losses = gameHistory.filter(game => !game.won).length; | |
| winsDisplay.textContent = wins; | |
| lossesDisplay.textContent = losses; | |
| } | |
| // Show final statistics | |
| function showFinalStats() { | |
| gameContainer.classList.add('hidden'); | |
| finalStatsSection.classList.remove('hidden'); | |
| const totalGames = wins + losses; | |
| const successRate = totalGames > 0 ? Math.round((wins / totalGames) * 100) : 0; | |
| const totalMistakes = gameHistory.reduce((sum, game) => sum + game.mistakes, 0); | |
| const avgMistakes = totalGames > 0 ? (totalMistakes / totalGames).toFixed(1) : 0; | |
| // Calculate total words guessed | |
| let totalWords = 0; | |
| let guessedWords = 0; | |
| for (const category in wordCategories) { | |
| totalWords += wordCategories[category].words.length; | |
| guessedWords += usedWords[category].length; | |
| } | |
| finalWinsDisplay.textContent = wins; | |
| finalLossesDisplay.textContent = losses; | |
| wordsGuessedDisplay.textContent = `${guessedWords}/${totalWords}`; | |
| avgMistakesDisplay.textContent = avgMistakes; | |
| successRateDisplay.textContent = successRate; | |
| // Animate the progress ring | |
| const circumference = 2 * Math.PI * 90; | |
| const offset = circumference - (successRate / 100) * circumference; | |
| winRing.style.strokeDashoffset = offset; | |
| // Reset used words for a new game | |
| initUsedWords(); | |
| } | |
| // Show hint | |
| hintButton.addEventListener('click', function() { | |
| if (!hintUsed && gameActive) { | |
| hintArea.classList.remove('hidden'); | |
| hintButton.classList.add('hidden'); | |
| hintUsed = true; | |
| } | |
| }); | |
| // New game button | |
| newGameButton.addEventListener('click', initGame); | |
| // Restart game button (from final stats) | |
| restartGameButton.addEventListener('click', function() { | |
| finalStatsSection.classList.add('hidden'); | |
| gameContainer.classList.remove('hidden'); | |
| initGame(); | |
| }); | |
| // Toggle game history visibility | |
| toggleHistoryButton.addEventListener('click', function() { | |
| if (gameHistorySection.classList.contains('hidden')) { | |
| gameHistorySection.classList.remove('hidden'); | |
| toggleHistoryButton.innerHTML = '<i class="fas fa-eye-slash mr-2"></i>Verstecken'; | |
| } else { | |
| gameHistorySection.classList.add('hidden'); | |
| toggleHistoryButton.innerHTML = '<i class="fas fa-eye mr-2"></i>Anzeigen'; | |
| } | |
| }); | |
| // Create confetti effect | |
| function createConfetti() { | |
| const colors = ['#f00', '#0f0', '#00f', '#ff0', '#f0f', '#0ff']; | |
| const container = document.querySelector('main'); | |
| for (let i = 0; i < 100; i++) { | |
| const confetti = document.createElement('div'); | |
| confetti.className = 'confetti'; | |
| confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)]; | |
| confetti.style.left = `${Math.random() * 100}%`; | |
| confetti.style.top = '-10px'; | |
| confetti.style.transform = `rotate(${Math.random() * 360}deg)`; | |
| container.appendChild(confetti); | |
| setTimeout(() => { | |
| confetti.style.opacity = '1'; | |
| confetti.style.top = `${Math.random() * 100}%`; | |
| confetti.style.left = `${Math.random() * 100}%`; | |
| confetti.style.transition = `all ${3 + Math.random() * 5}s ease-out`; | |
| setTimeout(() => { | |
| confetti.style.opacity = '0'; | |
| setTimeout(() => confetti.remove(), 1000); | |
| }, 3000); | |
| }, 0); | |
| } | |
| } | |
| // Load used words from localStorage | |
| const savedUsedWords = localStorage.getItem('norasUsedWords'); | |
| if (savedUsedWords) { | |
| usedWords = JSON.parse(savedUsedWords); | |
| } else { | |
| initUsedWords(); | |
| } | |
| // Start the game | |
| initGame(); | |
| updateStats(); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=ThetaPhiPsi/noras-hangman" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |