feat: implement daily character guessing game with local storage and hint system

- Added character selection and history management using local storage.
- Implemented hint system that unlocks based on the number of guesses.
- Enhanced UI with animations for hint unlocks and special win conditions.
- Created a server endpoint to record wins in the database.
This commit is contained in:
2026-03-01 03:59:16 +01:00
parent 6f7bae2307
commit b8b3f8bddc
23 changed files with 2988 additions and 620 deletions

View File

@@ -0,0 +1,152 @@
import { db } from '$lib/server/db';
import { arc, character, characterHistory, devilFruit } from '$lib/server/db/schema';
import { desc, eq } from 'drizzle-orm';
const characterWithRelationsSelect = {
id: character.id,
name: character.name,
gender: character.gender,
age: character.age,
affiliations: character.affiliations,
devilFruitId: character.devilFruitId,
devilFruitName: devilFruit.name,
devilFruitType: devilFruit.type,
hakiObservation: character.hakiObservation,
hakiArmament: character.hakiArmament,
hakiConqueror: character.hakiConqueror,
bounty: character.bounty,
height: character.height,
origin: character.origin,
firstAppearance: character.firstAppearance,
pictureUrl: character.pictureUrl,
epithets: character.epithets,
status: character.status,
url: character.url,
arcId: character.arcId,
arcName: arc.name
};
export type CharacterWithRelations = typeof character.$inferSelect & {
devilFruitName: string | null;
devilFruitType: string | null;
arcName: string | null;
};
function getDateKey(date: Date): string {
return date.toISOString().split('T')[0];
}
function normalizeDay(date: Date = new Date()): Date {
const normalized = new Date(date);
normalized.setHours(0, 0, 0, 0);
return normalized;
}
function pickDailyCharacter(characters: CharacterWithRelations[], date: Date): CharacterWithRelations {
const dateStr = getDateKey(date);
const seed = dateStr.split('-').reduce((acc, value) => acc + parseInt(value), 0);
const index = seed % characters.length;
return characters[index];
}
export async function getDailyModeCharacters(): Promise<CharacterWithRelations[]> {
return db
.select(characterWithRelationsSelect)
.from(character)
.leftJoin(arc, eq(character.arcId, arc.id))
.leftJoin(devilFruit, eq(character.devilFruitId, devilFruit.id))
.where(eq(character.isInDailyMode, true))
.all() as Promise<CharacterWithRelations[]>;
}
export async function getCharacterById(characterId: string): Promise<CharacterWithRelations | null> {
const [found] = await db
.select(characterWithRelationsSelect)
.from(character)
.leftJoin(arc, eq(character.arcId, arc.id))
.leftJoin(devilFruit, eq(character.devilFruitId, devilFruit.id))
.where(eq(character.id, characterId))
.limit(1);
return (found ?? null) as CharacterWithRelations | null;
}
export async function getOrCreateTodayCharacter(
characters: CharacterWithRelations[],
date: Date = new Date()
): Promise<CharacterWithRelations | null> {
if (characters.length === 0) {
return null;
}
const today = normalizeDay(date);
const todayDate = getDateKey(today);
const [existingEntry] = await db
.select()
.from(characterHistory)
.where(eq(characterHistory.date, todayDate))
.limit(1);
if (existingEntry?.characterId) {
return (
characters.find((currentCharacter) => currentCharacter.id === existingEntry.characterId) ??
(await getCharacterById(existingEntry.characterId))
);
}
const recentHistory = await db
.select({ characterId: characterHistory.characterId })
.from(characterHistory)
.orderBy(desc(characterHistory.date))
.limit(100);
const excludedIds = new Set(recentHistory.map((entry) => entry.characterId));
const availableCharacters = characters.filter((currentCharacter) => !excludedIds.has(currentCharacter.id));
const dailyCharacter = pickDailyCharacter(
availableCharacters.length > 0 ? availableCharacters : characters,
today
);
try {
await db.insert(characterHistory).values({
characterId: dailyCharacter.id,
date: todayDate,
createdAt: Date.now(),
updatedAt: Date.now()
});
} catch (error) {
console.error('Failed to record daily character:', error);
}
return dailyCharacter;
}
export async function getYesterdayCharacter(
date: Date = new Date(),
characters?: CharacterWithRelations[]
): Promise<CharacterWithRelations | null> {
const baseDate = normalizeDay(date);
baseDate.setDate(baseDate.getDate() - 1);
const yesterdayDate = getDateKey(baseDate);
const [yesterdayEntry] = await db
.select()
.from(characterHistory)
.where(eq(characterHistory.date, yesterdayDate))
.limit(1);
if (!yesterdayEntry?.characterId) {
return null;
}
if (characters) {
return (
characters.find((currentCharacter) => currentCharacter.id === yesterdayEntry.characterId) ??
(await getCharacterById(yesterdayEntry.characterId))
);
}
return getCharacterById(yesterdayEntry.characterId);
}