Implement Specials feature, Admin UI enhancements, and Database Rebuild tool
This commit is contained in:
@@ -111,3 +111,92 @@ export async function getOrCreateDailyPuzzle(genreName: string | null = null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getOrCreateSpecialPuzzle(specialName: string) {
|
||||
try {
|
||||
const today = getTodayISOString();
|
||||
|
||||
const special = await prisma.special.findUnique({
|
||||
where: { name: specialName }
|
||||
});
|
||||
|
||||
if (!special) return null;
|
||||
|
||||
let dailyPuzzle = await prisma.dailyPuzzle.findFirst({
|
||||
where: {
|
||||
date: today,
|
||||
specialId: special.id
|
||||
},
|
||||
include: { song: true },
|
||||
});
|
||||
|
||||
if (!dailyPuzzle) {
|
||||
// Get songs available for this special
|
||||
const allSongs = await prisma.song.findMany({
|
||||
where: { specials: { some: { id: special.id } } },
|
||||
include: {
|
||||
puzzles: {
|
||||
where: { specialId: special.id }
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (allSongs.length === 0) return null;
|
||||
|
||||
// Calculate weights
|
||||
const weightedSongs = allSongs.map(song => ({
|
||||
song,
|
||||
weight: 1.0 / (song.puzzles.length + 1),
|
||||
}));
|
||||
|
||||
const totalWeight = weightedSongs.reduce((sum, item) => sum + item.weight, 0);
|
||||
let random = Math.random() * totalWeight;
|
||||
let selectedSong = weightedSongs[0].song;
|
||||
|
||||
for (const item of weightedSongs) {
|
||||
random -= item.weight;
|
||||
if (random <= 0) {
|
||||
selectedSong = item.song;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
dailyPuzzle = await prisma.dailyPuzzle.create({
|
||||
data: {
|
||||
date: today,
|
||||
songId: selectedSong.id,
|
||||
specialId: special.id
|
||||
},
|
||||
include: { song: true },
|
||||
});
|
||||
} catch (e) {
|
||||
dailyPuzzle = await prisma.dailyPuzzle.findFirst({
|
||||
where: {
|
||||
date: today,
|
||||
specialId: special.id
|
||||
},
|
||||
include: { song: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!dailyPuzzle) return null;
|
||||
|
||||
return {
|
||||
id: dailyPuzzle.id,
|
||||
audioUrl: `/uploads/${dailyPuzzle.song.filename}`,
|
||||
songId: dailyPuzzle.songId,
|
||||
title: dailyPuzzle.song.title,
|
||||
artist: dailyPuzzle.song.artist,
|
||||
coverImage: dailyPuzzle.song.coverImage ? `/uploads/covers/${dailyPuzzle.song.coverImage}` : null,
|
||||
special: specialName,
|
||||
maxAttempts: special.maxAttempts,
|
||||
unlockSteps: JSON.parse(special.unlockSteps)
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in getOrCreateSpecialPuzzle:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface Statistics {
|
||||
const STORAGE_KEY = 'hoerdle_game_state';
|
||||
const STATS_KEY = 'hoerdle_statistics';
|
||||
|
||||
export function useGameState(genre: string | null = null) {
|
||||
export function useGameState(genre: string | null = null, maxAttempts: number = 7) {
|
||||
const [gameState, setGameState] = useState<GameState | null>(null);
|
||||
const [statistics, setStatistics] = useState<Statistics | null>(null);
|
||||
|
||||
@@ -115,6 +115,10 @@ export function useGameState(genre: string | null = null) {
|
||||
case 5: newStats.solvedIn5++; break;
|
||||
case 6: newStats.solvedIn6++; break;
|
||||
case 7: newStats.solvedIn7++; break;
|
||||
default:
|
||||
// For custom attempts > 7, we currently don't have specific stats buckets
|
||||
// We could add a 'solvedInOther' or just ignore for now
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
newStats.failed++;
|
||||
@@ -129,7 +133,7 @@ export function useGameState(genre: string | null = null) {
|
||||
|
||||
const newGuesses = [...gameState.guesses, guess];
|
||||
const isSolved = correct;
|
||||
const isFailed = !correct && newGuesses.length >= 7;
|
||||
const isFailed = !correct && newGuesses.length >= maxAttempts;
|
||||
|
||||
const newState = {
|
||||
...gameState,
|
||||
|
||||
Reference in New Issue
Block a user