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();
|