feat: add 7th guess with 60s unlock and update docs

This commit is contained in:
Hördle Bot
2025-11-21 19:25:38 +01:00
parent 7ce45be90d
commit 95a3b09f52
4 changed files with 20 additions and 9 deletions

View File

@@ -5,7 +5,7 @@ Eine Web-App inspiriert von Heardle, bei der Nutzer täglich einen Song anhand k
## Features ## Features
- **Tägliches Rätsel:** Jeden Tag ein neuer Song für alle Nutzer. - **Tägliches Rätsel:** Jeden Tag ein neuer Song für alle Nutzer.
- **Inkrementelle Hinweise:** Startet mit 2 Sekunden, dann 4s, 7s, 11s, 16s, bis 30s. - **Inkrementelle Hinweise:** Startet mit 2 Sekunden, dann 4s, 7s, 11s, 16s, 30s, bis 60s (7 Versuche).
- **Admin Dashboard:** - **Admin Dashboard:**
- Upload von MP3-Dateien. - Upload von MP3-Dateien.
- Automatische Extraktion von ID3-Tags (Titel, Interpret). - Automatische Extraktion von ID3-Tags (Titel, Interpret).

View File

@@ -17,7 +17,7 @@ interface GameProps {
} | null; } | null;
} }
const UNLOCK_STEPS = [2, 4, 7, 11, 16, 30]; const UNLOCK_STEPS = [2, 4, 7, 11, 16, 30, 60];
export default function Game({ dailyPuzzle }: GameProps) { export default function Game({ dailyPuzzle }: GameProps) {
const { gameState, statistics, addGuess } = useGameState(); const { gameState, statistics, addGuess } = useGameState();
@@ -48,17 +48,17 @@ export default function Game({ dailyPuzzle }: GameProps) {
setHasWon(true); setHasWon(true);
} else { } else {
addGuess(song.title, false); addGuess(song.title, false);
if (gameState.guesses.length + 1 >= 6) { if (gameState.guesses.length + 1 >= 7) {
setHasLost(true); setHasLost(true);
} }
} }
}; };
const unlockedSeconds = UNLOCK_STEPS[Math.min(gameState.guesses.length, 5)]; const unlockedSeconds = UNLOCK_STEPS[Math.min(gameState.guesses.length, 6)];
const handleShare = () => { const handleShare = () => {
let emojiGrid = ''; let emojiGrid = '';
const totalGuesses = 6; const totalGuesses = 7;
// Build the grid // Build the grid
for (let i = 0; i < totalGuesses; i++) { for (let i = 0; i < totalGuesses; i++) {
@@ -110,7 +110,7 @@ export default function Game({ dailyPuzzle }: GameProps) {
<div style={{ borderBottom: '1px solid #e5e7eb', paddingBottom: '1rem' }}> <div style={{ borderBottom: '1px solid #e5e7eb', paddingBottom: '1rem' }}>
<div className="status-bar"> <div className="status-bar">
<span>Attempt {gameState.guesses.length + 1} / 6</span> <span>Attempt {gameState.guesses.length + 1} / 7</span>
<span>{unlockedSeconds}s unlocked</span> <span>{unlockedSeconds}s unlocked</span>
</div> </div>
<AudioPlayer <AudioPlayer
@@ -140,7 +140,7 @@ export default function Game({ dailyPuzzle }: GameProps) {
onClick={() => addGuess("SKIPPED", false)} onClick={() => addGuess("SKIPPED", false)}
className="skip-button" className="skip-button"
> >
Skip (+{UNLOCK_STEPS[Math.min(gameState.guesses.length + 1, 5)] - unlockedSeconds}s) Skip (+{UNLOCK_STEPS[Math.min(gameState.guesses.length + 1, 6)] - unlockedSeconds}s)
</button> </button>
</> </>
)} )}

View File

@@ -13,6 +13,7 @@ const BADGES = {
4: '⭐', // Star 4: '⭐', // Star
5: '✨', // Sparkles 5: '✨', // Sparkles
6: '💫', // Dizzy 6: '💫', // Dizzy
7: '🎵', // Musical note
failed: '❌', // Cross mark failed: '❌', // Cross mark
}; };
@@ -24,6 +25,7 @@ export default function Statistics({ statistics }: StatisticsProps) {
statistics.solvedIn4 + statistics.solvedIn4 +
statistics.solvedIn5 + statistics.solvedIn5 +
statistics.solvedIn6 + statistics.solvedIn6 +
statistics.solvedIn7 +
statistics.failed; statistics.failed;
const stats = [ const stats = [
@@ -33,6 +35,7 @@ export default function Statistics({ statistics }: StatisticsProps) {
{ attempts: 4, count: statistics.solvedIn4, badge: BADGES[4] }, { attempts: 4, count: statistics.solvedIn4, badge: BADGES[4] },
{ attempts: 5, count: statistics.solvedIn5, badge: BADGES[5] }, { attempts: 5, count: statistics.solvedIn5, badge: BADGES[5] },
{ attempts: 6, count: statistics.solvedIn6, badge: BADGES[6] }, { attempts: 6, count: statistics.solvedIn6, badge: BADGES[6] },
{ attempts: 7, count: statistics.solvedIn7, badge: BADGES[7] },
{ attempts: 'Failed', count: statistics.failed, badge: BADGES.failed }, { attempts: 'Failed', count: statistics.failed, badge: BADGES.failed },
]; ];

View File

@@ -17,6 +17,7 @@ export interface Statistics {
solvedIn4: number; solvedIn4: number;
solvedIn5: number; solvedIn5: number;
solvedIn6: number; solvedIn6: number;
solvedIn7: number;
failed: number; failed: number;
} }
@@ -64,7 +65,12 @@ export function useGameState() {
// Load statistics // Load statistics
const storedStats = localStorage.getItem(STATS_KEY); const storedStats = localStorage.getItem(STATS_KEY);
if (storedStats) { if (storedStats) {
setStatistics(JSON.parse(storedStats)); const parsedStats = JSON.parse(storedStats);
// Migration for existing stats without solvedIn7
if (parsedStats.solvedIn7 === undefined) {
parsedStats.solvedIn7 = 0;
}
setStatistics(parsedStats);
} else { } else {
const newStats: Statistics = { const newStats: Statistics = {
solvedIn1: 0, solvedIn1: 0,
@@ -73,6 +79,7 @@ export function useGameState() {
solvedIn4: 0, solvedIn4: 0,
solvedIn5: 0, solvedIn5: 0,
solvedIn6: 0, solvedIn6: 0,
solvedIn7: 0,
failed: 0, failed: 0,
}; };
setStatistics(newStats); setStatistics(newStats);
@@ -98,6 +105,7 @@ export function useGameState() {
case 4: newStats.solvedIn4++; break; case 4: newStats.solvedIn4++; break;
case 5: newStats.solvedIn5++; break; case 5: newStats.solvedIn5++; break;
case 6: newStats.solvedIn6++; break; case 6: newStats.solvedIn6++; break;
case 7: newStats.solvedIn7++; break;
} }
} else { } else {
newStats.failed++; newStats.failed++;
@@ -112,7 +120,7 @@ export function useGameState() {
const newGuesses = [...gameState.guesses, guess]; const newGuesses = [...gameState.guesses, guess];
const isSolved = correct; const isSolved = correct;
const isFailed = !correct && newGuesses.length >= 6; const isFailed = !correct && newGuesses.length >= 7;
const newState = { const newState = {
...gameState, ...gameState,