Spaces:
Running
Running
Add 1 files
Browse files- index.html +152 -8
index.html
CHANGED
|
@@ -44,6 +44,16 @@
|
|
| 44 |
.sad-face {
|
| 45 |
transform: rotate(180deg);
|
| 46 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
</style>
|
| 48 |
</head>
|
| 49 |
<body class="bg-blue-50 min-h-screen flex flex-col items-center font-sans">
|
|
@@ -54,7 +64,7 @@
|
|
| 54 |
</header>
|
| 55 |
|
| 56 |
<main class="container mx-auto px-4 py-8 flex-grow flex flex-col items-center">
|
| 57 |
-
<div class="w-full max-w-4xl bg-white rounded-xl shadow-lg p-6 mb-8">
|
| 58 |
<div class="flex flex-col md:flex-row gap-8">
|
| 59 |
<!-- Hangman Drawing -->
|
| 60 |
<div class="w-full md:w-1/2 flex flex-col items-center">
|
|
@@ -92,6 +102,7 @@
|
|
| 92 |
<div class="w-full md:w-1/2 flex flex-col">
|
| 93 |
<div class="mb-6">
|
| 94 |
<p class="text-gray-600 mb-2">Kategorie: <span id="category" class="font-semibold">Tiere</span></p>
|
|
|
|
| 95 |
<div id="word-display" class="flex justify-center gap-2 mb-6 flex-wrap"></div>
|
| 96 |
</div>
|
| 97 |
|
|
@@ -111,7 +122,7 @@
|
|
| 111 |
<div id="message" class="hidden mb-4 p-3 rounded-lg text-center font-bold"></div>
|
| 112 |
|
| 113 |
<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">
|
| 114 |
-
|
| 115 |
</button>
|
| 116 |
</div>
|
| 117 |
</div>
|
|
@@ -146,6 +157,46 @@
|
|
| 146 |
</div>
|
| 147 |
</div>
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
<div class="w-full max-w-4xl bg-white rounded-xl shadow-lg p-6">
|
| 150 |
<h2 class="text-xl font-bold mb-4 text-center">Wie spielt man?</h2>
|
| 151 |
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
@@ -292,6 +343,16 @@
|
|
| 292 |
let gameHistory = JSON.parse(localStorage.getItem('norasGameHistory')) || [];
|
| 293 |
let wins = 0;
|
| 294 |
let losses = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
|
| 296 |
// DOM elements
|
| 297 |
const wordDisplay = document.getElementById('word-display');
|
|
@@ -318,6 +379,16 @@
|
|
| 318 |
const wrongSound = document.getElementById('wrong-sound');
|
| 319 |
const winSound = document.getElementById('win-sound');
|
| 320 |
const loseSound = document.getElementById('lose-sound');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 321 |
|
| 322 |
// Initialize the game
|
| 323 |
function initGame() {
|
|
@@ -344,17 +415,33 @@
|
|
| 344 |
face.classList.remove('sad-face');
|
| 345 |
face.classList.add('happy-face');
|
| 346 |
|
| 347 |
-
// Select a random category
|
| 348 |
-
const
|
| 349 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
categoryDisplay.textContent = wordCategories[currentCategory].name;
|
| 351 |
|
| 352 |
-
// Select a random word from the category
|
| 353 |
-
const
|
| 354 |
-
|
|
|
|
|
|
|
|
|
|
| 355 |
currentWord = randomWordObj.word;
|
| 356 |
hintText.textContent = randomWordObj.hint;
|
| 357 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
// Update UI
|
| 359 |
updateMistakesDisplay();
|
| 360 |
createWordDisplay();
|
|
@@ -489,6 +576,9 @@
|
|
| 489 |
winSound.currentTime = 0;
|
| 490 |
winSound.play();
|
| 491 |
|
|
|
|
|
|
|
|
|
|
| 492 |
// Add to game history
|
| 493 |
addToGameHistory(true);
|
| 494 |
createConfetti();
|
|
@@ -508,6 +598,11 @@
|
|
| 508 |
loseSound.currentTime = 0;
|
| 509 |
loseSound.play();
|
| 510 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
// Add to game history
|
| 512 |
addToGameHistory(false);
|
| 513 |
|
|
@@ -554,6 +649,7 @@
|
|
| 554 |
|
| 555 |
// Save to localStorage
|
| 556 |
localStorage.setItem('norasGameHistory', JSON.stringify(gameHistory));
|
|
|
|
| 557 |
|
| 558 |
// Update stats
|
| 559 |
updateStats();
|
|
@@ -592,6 +688,39 @@
|
|
| 592 |
lossesDisplay.textContent = losses;
|
| 593 |
}
|
| 594 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 595 |
// Show hint
|
| 596 |
hintButton.addEventListener('click', function() {
|
| 597 |
if (!hintUsed && gameActive) {
|
|
@@ -604,6 +733,13 @@
|
|
| 604 |
// New game button
|
| 605 |
newGameButton.addEventListener('click', initGame);
|
| 606 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 607 |
// Toggle game history visibility
|
| 608 |
toggleHistoryButton.addEventListener('click', function() {
|
| 609 |
if (gameHistorySection.classList.contains('hidden')) {
|
|
@@ -644,6 +780,14 @@
|
|
| 644 |
}
|
| 645 |
}
|
| 646 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 647 |
// Start the game
|
| 648 |
initGame();
|
| 649 |
updateStats();
|
|
|
|
| 44 |
.sad-face {
|
| 45 |
transform: rotate(180deg);
|
| 46 |
}
|
| 47 |
+
.final-stats {
|
| 48 |
+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
| 49 |
+
border-radius: 1rem;
|
| 50 |
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
| 51 |
+
}
|
| 52 |
+
.progress-ring {
|
| 53 |
+
transition: stroke-dashoffset 0.5s;
|
| 54 |
+
transform: rotate(-90deg);
|
| 55 |
+
transform-origin: 50% 50%;
|
| 56 |
+
}
|
| 57 |
</style>
|
| 58 |
</head>
|
| 59 |
<body class="bg-blue-50 min-h-screen flex flex-col items-center font-sans">
|
|
|
|
| 64 |
</header>
|
| 65 |
|
| 66 |
<main class="container mx-auto px-4 py-8 flex-grow flex flex-col items-center">
|
| 67 |
+
<div id="game-container" class="w-full max-w-4xl bg-white rounded-xl shadow-lg p-6 mb-8">
|
| 68 |
<div class="flex flex-col md:flex-row gap-8">
|
| 69 |
<!-- Hangman Drawing -->
|
| 70 |
<div class="w-full md:w-1/2 flex flex-col items-center">
|
|
|
|
| 102 |
<div class="w-full md:w-1/2 flex flex-col">
|
| 103 |
<div class="mb-6">
|
| 104 |
<p class="text-gray-600 mb-2">Kategorie: <span id="category" class="font-semibold">Tiere</span></p>
|
| 105 |
+
<p class="text-gray-600 mb-2">Wörter übrig: <span id="words-left" class="font-semibold">10</span></p>
|
| 106 |
<div id="word-display" class="flex justify-center gap-2 mb-6 flex-wrap"></div>
|
| 107 |
</div>
|
| 108 |
|
|
|
|
| 122 |
<div id="message" class="hidden mb-4 p-3 rounded-lg text-center font-bold"></div>
|
| 123 |
|
| 124 |
<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">
|
| 125 |
+
Nächstes Wort
|
| 126 |
</button>
|
| 127 |
</div>
|
| 128 |
</div>
|
|
|
|
| 157 |
</div>
|
| 158 |
</div>
|
| 159 |
|
| 160 |
+
<!-- Final Statistics -->
|
| 161 |
+
<div id="final-stats" class="w-full max-w-4xl final-stats p-8 mb-8 hidden">
|
| 162 |
+
<h2 class="text-3xl font-bold text-center mb-6">Spiel beendet! 🎉</h2>
|
| 163 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 items-center">
|
| 164 |
+
<div>
|
| 165 |
+
<h3 class="text-xl font-semibold mb-4">Zusammenfassung</h3>
|
| 166 |
+
<div class="space-y-4">
|
| 167 |
+
<div class="flex justify-between items-center">
|
| 168 |
+
<span class="text-gray-700">Gewonnene Spiele:</span>
|
| 169 |
+
<span id="final-wins" class="font-bold text-green-600">0</span>
|
| 170 |
+
</div>
|
| 171 |
+
<div class="flex justify-between items-center">
|
| 172 |
+
<span class="text-gray-700">Verlorene Spiele:</span>
|
| 173 |
+
<span id="final-losses" class="font-bold text-red-600">0</span>
|
| 174 |
+
</div>
|
| 175 |
+
<div class="flex justify-between items-center">
|
| 176 |
+
<span class="text-gray-700">Wörter erraten:</span>
|
| 177 |
+
<span id="words-guessed" class="font-bold text-blue-600">0/0</span>
|
| 178 |
+
</div>
|
| 179 |
+
<div class="flex justify-between items-center">
|
| 180 |
+
<span class="text-gray-700">Durchschnittliche Fehler:</span>
|
| 181 |
+
<span id="avg-mistakes" class="font-bold text-purple-600">0</span>
|
| 182 |
+
</div>
|
| 183 |
+
</div>
|
| 184 |
+
</div>
|
| 185 |
+
<div class="flex flex-col items-center">
|
| 186 |
+
<svg width="200" height="200" viewBox="0 0 200 200" class="mb-4">
|
| 187 |
+
<circle cx="100" cy="100" r="90" fill="none" stroke="#e5e7eb" stroke-width="10"/>
|
| 188 |
+
<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"/>
|
| 189 |
+
</svg>
|
| 190 |
+
<p class="text-center text-gray-700">Erfolgsquote: <span id="success-rate" class="font-bold">0</span>%</p>
|
| 191 |
+
</div>
|
| 192 |
+
</div>
|
| 193 |
+
<div class="mt-8 text-center">
|
| 194 |
+
<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">
|
| 195 |
+
<i class="fas fa-redo mr-2"></i>Neues Spiel starten
|
| 196 |
+
</button>
|
| 197 |
+
</div>
|
| 198 |
+
</div>
|
| 199 |
+
|
| 200 |
<div class="w-full max-w-4xl bg-white rounded-xl shadow-lg p-6">
|
| 201 |
<h2 class="text-xl font-bold mb-4 text-center">Wie spielt man?</h2>
|
| 202 |
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
|
|
| 343 |
let gameHistory = JSON.parse(localStorage.getItem('norasGameHistory')) || [];
|
| 344 |
let wins = 0;
|
| 345 |
let losses = 0;
|
| 346 |
+
let usedWords = {};
|
| 347 |
+
let wordsLeft = 0;
|
| 348 |
+
|
| 349 |
+
// Initialize usedWords object
|
| 350 |
+
function initUsedWords() {
|
| 351 |
+
usedWords = {};
|
| 352 |
+
for (const category in wordCategories) {
|
| 353 |
+
usedWords[category] = [];
|
| 354 |
+
}
|
| 355 |
+
}
|
| 356 |
|
| 357 |
// DOM elements
|
| 358 |
const wordDisplay = document.getElementById('word-display');
|
|
|
|
| 379 |
const wrongSound = document.getElementById('wrong-sound');
|
| 380 |
const winSound = document.getElementById('win-sound');
|
| 381 |
const loseSound = document.getElementById('lose-sound');
|
| 382 |
+
const wordsLeftDisplay = document.getElementById('words-left');
|
| 383 |
+
const finalStatsSection = document.getElementById('final-stats');
|
| 384 |
+
const finalWinsDisplay = document.getElementById('final-wins');
|
| 385 |
+
const finalLossesDisplay = document.getElementById('final-losses');
|
| 386 |
+
const wordsGuessedDisplay = document.getElementById('words-guessed');
|
| 387 |
+
const avgMistakesDisplay = document.getElementById('avg-mistakes');
|
| 388 |
+
const successRateDisplay = document.getElementById('success-rate');
|
| 389 |
+
const restartGameButton = document.getElementById('restart-game-button');
|
| 390 |
+
const winRing = document.getElementById('win-ring');
|
| 391 |
+
const gameContainer = document.getElementById('game-container');
|
| 392 |
|
| 393 |
// Initialize the game
|
| 394 |
function initGame() {
|
|
|
|
| 415 |
face.classList.remove('sad-face');
|
| 416 |
face.classList.add('happy-face');
|
| 417 |
|
| 418 |
+
// Select a random category that still has words left
|
| 419 |
+
const availableCategories = Object.keys(wordCategories).filter(category => {
|
| 420 |
+
return wordCategories[category].words.length > usedWords[category].length;
|
| 421 |
+
});
|
| 422 |
+
|
| 423 |
+
// If no categories have words left, show final stats
|
| 424 |
+
if (availableCategories.length === 0) {
|
| 425 |
+
showFinalStats();
|
| 426 |
+
return;
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
currentCategory = availableCategories[Math.floor(Math.random() * availableCategories.length)];
|
| 430 |
categoryDisplay.textContent = wordCategories[currentCategory].name;
|
| 431 |
|
| 432 |
+
// Select a random word from the category that hasn't been used yet
|
| 433 |
+
const availableWords = wordCategories[currentCategory].words.filter(wordObj => {
|
| 434 |
+
return !usedWords[currentCategory].includes(wordObj.word);
|
| 435 |
+
});
|
| 436 |
+
|
| 437 |
+
const randomWordObj = availableWords[Math.floor(Math.random() * availableWords.length)];
|
| 438 |
currentWord = randomWordObj.word;
|
| 439 |
hintText.textContent = randomWordObj.hint;
|
| 440 |
|
| 441 |
+
// Update words left display
|
| 442 |
+
wordsLeft = wordCategories[currentCategory].words.length - usedWords[currentCategory].length - 1;
|
| 443 |
+
wordsLeftDisplay.textContent = wordsLeft;
|
| 444 |
+
|
| 445 |
// Update UI
|
| 446 |
updateMistakesDisplay();
|
| 447 |
createWordDisplay();
|
|
|
|
| 576 |
winSound.currentTime = 0;
|
| 577 |
winSound.play();
|
| 578 |
|
| 579 |
+
// Mark word as used
|
| 580 |
+
usedWords[currentCategory].push(currentWord);
|
| 581 |
+
|
| 582 |
// Add to game history
|
| 583 |
addToGameHistory(true);
|
| 584 |
createConfetti();
|
|
|
|
| 598 |
loseSound.currentTime = 0;
|
| 599 |
loseSound.play();
|
| 600 |
|
| 601 |
+
// Mark word as used (only if not already marked)
|
| 602 |
+
if (!usedWords[currentCategory].includes(currentWord)) {
|
| 603 |
+
usedWords[currentCategory].push(currentWord);
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
// Add to game history
|
| 607 |
addToGameHistory(false);
|
| 608 |
|
|
|
|
| 649 |
|
| 650 |
// Save to localStorage
|
| 651 |
localStorage.setItem('norasGameHistory', JSON.stringify(gameHistory));
|
| 652 |
+
localStorage.setItem('norasUsedWords', JSON.stringify(usedWords));
|
| 653 |
|
| 654 |
// Update stats
|
| 655 |
updateStats();
|
|
|
|
| 688 |
lossesDisplay.textContent = losses;
|
| 689 |
}
|
| 690 |
|
| 691 |
+
// Show final statistics
|
| 692 |
+
function showFinalStats() {
|
| 693 |
+
gameContainer.classList.add('hidden');
|
| 694 |
+
finalStatsSection.classList.remove('hidden');
|
| 695 |
+
|
| 696 |
+
const totalGames = wins + losses;
|
| 697 |
+
const successRate = totalGames > 0 ? Math.round((wins / totalGames) * 100) : 0;
|
| 698 |
+
const totalMistakes = gameHistory.reduce((sum, game) => sum + game.mistakes, 0);
|
| 699 |
+
const avgMistakes = totalGames > 0 ? (totalMistakes / totalGames).toFixed(1) : 0;
|
| 700 |
+
|
| 701 |
+
// Calculate total words guessed
|
| 702 |
+
let totalWords = 0;
|
| 703 |
+
let guessedWords = 0;
|
| 704 |
+
for (const category in wordCategories) {
|
| 705 |
+
totalWords += wordCategories[category].words.length;
|
| 706 |
+
guessedWords += usedWords[category].length;
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
finalWinsDisplay.textContent = wins;
|
| 710 |
+
finalLossesDisplay.textContent = losses;
|
| 711 |
+
wordsGuessedDisplay.textContent = `${guessedWords}/${totalWords}`;
|
| 712 |
+
avgMistakesDisplay.textContent = avgMistakes;
|
| 713 |
+
successRateDisplay.textContent = successRate;
|
| 714 |
+
|
| 715 |
+
// Animate the progress ring
|
| 716 |
+
const circumference = 2 * Math.PI * 90;
|
| 717 |
+
const offset = circumference - (successRate / 100) * circumference;
|
| 718 |
+
winRing.style.strokeDashoffset = offset;
|
| 719 |
+
|
| 720 |
+
// Reset used words for a new game
|
| 721 |
+
initUsedWords();
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
// Show hint
|
| 725 |
hintButton.addEventListener('click', function() {
|
| 726 |
if (!hintUsed && gameActive) {
|
|
|
|
| 733 |
// New game button
|
| 734 |
newGameButton.addEventListener('click', initGame);
|
| 735 |
|
| 736 |
+
// Restart game button (from final stats)
|
| 737 |
+
restartGameButton.addEventListener('click', function() {
|
| 738 |
+
finalStatsSection.classList.add('hidden');
|
| 739 |
+
gameContainer.classList.remove('hidden');
|
| 740 |
+
initGame();
|
| 741 |
+
});
|
| 742 |
+
|
| 743 |
// Toggle game history visibility
|
| 744 |
toggleHistoryButton.addEventListener('click', function() {
|
| 745 |
if (gameHistorySection.classList.contains('hidden')) {
|
|
|
|
| 780 |
}
|
| 781 |
}
|
| 782 |
|
| 783 |
+
// Load used words from localStorage
|
| 784 |
+
const savedUsedWords = localStorage.getItem('norasUsedWords');
|
| 785 |
+
if (savedUsedWords) {
|
| 786 |
+
usedWords = JSON.parse(savedUsedWords);
|
| 787 |
+
} else {
|
| 788 |
+
initUsedWords();
|
| 789 |
+
}
|
| 790 |
+
|
| 791 |
// Start the game
|
| 792 |
initGame();
|
| 793 |
updateStats();
|