feat: Enhance Game component with extra puzzles feature

- Introduce requiredDailyKeys to track daily puzzle completion across genres.
- Implement logic to show an ExtraPuzzlesPopover when all daily puzzles are completed.
- Add localized messages for extra puzzles in both English and German.
- Update GenrePage and Home components to pass requiredDailyKeys to the Game component.
This commit is contained in:
Hördle Bot
2025-12-02 10:59:22 +01:00
parent 0bfcf0737e
commit 8239753911
8 changed files with 282 additions and 3 deletions

47
lib/externalPuzzles.ts Normal file
View File

@@ -0,0 +1,47 @@
export type ExternalPuzzle = {
id: string;
nameDe: string;
nameEn: string;
url: string;
isActive?: boolean;
};
/**
* Zentrale Liste externer Rätselangebote.
*
* Erweiterung: Einfach neuen Eintrag in dieses Array hinzufügen.
*/
export const externalPuzzles: ExternalPuzzle[] = [
{
id: 'pastpuzzle',
nameDe: 'Past Puzzle',
nameEn: 'Past Puzzle',
url: 'https://www.pastpuzzle.de/#/',
isActive: true,
},
{
id: 'woerdle',
nameDe: 'Wördle',
nameEn: 'Wördle',
url: 'https://www.wördle.de',
isActive: true,
},
{
id: 'ciddle',
nameDe: 'Ciddle',
nameEn: 'Ciddle',
url: 'https://ciddle.winklerweb.net',
isActive: true,
},
];
export function getRandomExternalPuzzle(): ExternalPuzzle | null {
const activePuzzles = externalPuzzles.filter(p => p.isActive !== false);
if (activePuzzles.length === 0) {
return null;
}
const index = Math.floor(Math.random() * activePuzzles.length);
return activePuzzles[index] ?? null;
}

View File

@@ -0,0 +1,68 @@
import { getTodayISOString } from './dateUtils';
const DAILY_PLAYED_PREFIX = 'hoerdle_daily_played_';
const EXTRA_POPOVER_PREFIX = 'hoerdle_extra_puzzles_shown_';
function getTodayKey(prefix: string): string | null {
if (typeof window === 'undefined') return null;
const today = getTodayISOString();
return `${prefix}${today}`;
}
export function markDailyPuzzlePlayedToday(genreKey: string) {
const storageKey = getTodayKey(DAILY_PLAYED_PREFIX);
if (!storageKey) return;
try {
const raw = window.localStorage.getItem(storageKey);
const list: string[] = raw ? JSON.parse(raw) : [];
if (!list.includes(genreKey)) {
list.push(genreKey);
window.localStorage.setItem(storageKey, JSON.stringify(list));
}
} catch (e) {
console.warn('[extraPuzzles] Failed to mark daily puzzle as played', e);
}
}
export function hasPlayedAllDailyPuzzlesForToday(requiredGenreKeys: string[]): boolean {
const storageKey = getTodayKey(DAILY_PLAYED_PREFIX);
if (!storageKey) return false;
try {
const raw = window.localStorage.getItem(storageKey);
const played: string[] = raw ? JSON.parse(raw) : [];
if (!Array.isArray(played) || played.length === 0) {
return false;
}
return requiredGenreKeys.every(key => played.includes(key));
} catch (e) {
console.warn('[extraPuzzles] Failed to read played puzzles', e);
return false;
}
}
export function hasSeenExtraPuzzlesPopoverToday(): boolean {
const storageKey = getTodayKey(EXTRA_POPOVER_PREFIX);
if (!storageKey) return false;
try {
return window.localStorage.getItem(storageKey) === 'true';
} catch (e) {
console.warn('[extraPuzzles] Failed to read popover state', e);
return false;
}
}
export function markExtraPuzzlesPopoverShownToday() {
const storageKey = getTodayKey(EXTRA_POPOVER_PREFIX);
if (!storageKey) return;
try {
window.localStorage.setItem(storageKey, 'true');
} catch (e) {
console.warn('[extraPuzzles] Failed to persist popover state', e);
}
}