|
|
|
|
@@ -29,9 +29,7 @@ export async function getOrCreateDailyPuzzle(genre: Genre | null = null) {
|
|
|
|
|
const allSongs = await prisma.song.findMany({
|
|
|
|
|
where: whereClause,
|
|
|
|
|
include: {
|
|
|
|
|
puzzles: {
|
|
|
|
|
where: { genreId: genreId }
|
|
|
|
|
},
|
|
|
|
|
puzzles: true, // Load ALL puzzles, not just for this genre (to use total activations)
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@@ -40,28 +38,24 @@ export async function getOrCreateDailyPuzzle(genre: Genre | null = null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate weights
|
|
|
|
|
const weightedSongs = allSongs.map(song => ({
|
|
|
|
|
// Find songs with the minimum number of activations (all puzzles, not just for this genre)
|
|
|
|
|
// Only select from songs with the fewest activations to ensure fair distribution
|
|
|
|
|
const songsWithActivations = allSongs.map(song => ({
|
|
|
|
|
song,
|
|
|
|
|
weight: 1.0 / (song.puzzles.length + 1),
|
|
|
|
|
activations: song.puzzles.length,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Calculate total weight
|
|
|
|
|
const totalWeight = weightedSongs.reduce((sum, item) => sum + item.weight, 0);
|
|
|
|
|
// Find minimum activations
|
|
|
|
|
const minActivations = Math.min(...songsWithActivations.map(item => item.activations));
|
|
|
|
|
|
|
|
|
|
// Pick a random song based on weights using cumulative weights
|
|
|
|
|
// This ensures proper distribution and handles edge cases
|
|
|
|
|
let random = Math.random() * totalWeight;
|
|
|
|
|
let selectedSong = weightedSongs[weightedSongs.length - 1].song; // Fallback to last song
|
|
|
|
|
// Filter to only songs with minimum activations
|
|
|
|
|
const songsWithMinActivations = songsWithActivations
|
|
|
|
|
.filter(item => item.activations === minActivations)
|
|
|
|
|
.map(item => item.song);
|
|
|
|
|
|
|
|
|
|
let cumulativeWeight = 0;
|
|
|
|
|
for (const item of weightedSongs) {
|
|
|
|
|
cumulativeWeight += item.weight;
|
|
|
|
|
if (random <= cumulativeWeight) {
|
|
|
|
|
selectedSong = item.song;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Randomly select from songs with minimum activations
|
|
|
|
|
const randomIndex = Math.floor(Math.random() * songsWithMinActivations.length);
|
|
|
|
|
const selectedSong = songsWithMinActivations[randomIndex];
|
|
|
|
|
|
|
|
|
|
// Create the daily puzzle
|
|
|
|
|
try {
|
|
|
|
|
@@ -141,7 +135,7 @@ export async function getOrCreateSpecialPuzzle(special: Special) {
|
|
|
|
|
song: {
|
|
|
|
|
include: {
|
|
|
|
|
puzzles: {
|
|
|
|
|
where: { specialId: special.id }
|
|
|
|
|
where: { specialId: special.id } // For specials, only count puzzles within this special
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -150,25 +144,25 @@ export async function getOrCreateSpecialPuzzle(special: Special) {
|
|
|
|
|
|
|
|
|
|
if (specialSongs.length === 0) return null;
|
|
|
|
|
|
|
|
|
|
// Calculate weights
|
|
|
|
|
const weightedSongs = specialSongs.map(specialSong => ({
|
|
|
|
|
// Find songs with the minimum number of activations within this special
|
|
|
|
|
// Note: For specials, we only count puzzles within the special (not all puzzles),
|
|
|
|
|
// since specials are curated, separate lists
|
|
|
|
|
const songsWithActivations = specialSongs.map(specialSong => ({
|
|
|
|
|
specialSong,
|
|
|
|
|
weight: 1.0 / (specialSong.song.puzzles.length + 1),
|
|
|
|
|
activations: specialSong.song.puzzles.length,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const totalWeight = weightedSongs.reduce((sum, item) => sum + item.weight, 0);
|
|
|
|
|
let random = Math.random() * totalWeight;
|
|
|
|
|
let selectedSpecialSong = weightedSongs[weightedSongs.length - 1].specialSong; // Fallback to last song
|
|
|
|
|
// Find minimum activations
|
|
|
|
|
const minActivations = Math.min(...songsWithActivations.map(item => item.activations));
|
|
|
|
|
|
|
|
|
|
// Pick a random song based on weights using cumulative weights
|
|
|
|
|
let cumulativeWeight = 0;
|
|
|
|
|
for (const item of weightedSongs) {
|
|
|
|
|
cumulativeWeight += item.weight;
|
|
|
|
|
if (random <= cumulativeWeight) {
|
|
|
|
|
selectedSpecialSong = item.specialSong;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Filter to only songs with minimum activations
|
|
|
|
|
const songsWithMinActivations = songsWithActivations
|
|
|
|
|
.filter(item => item.activations === minActivations)
|
|
|
|
|
.map(item => item.specialSong);
|
|
|
|
|
|
|
|
|
|
// Randomly select from songs with minimum activations
|
|
|
|
|
const randomIndex = Math.floor(Math.random() * songsWithMinActivations.length);
|
|
|
|
|
const selectedSpecialSong = songsWithMinActivations[randomIndex];
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
dailyPuzzle = await prisma.dailyPuzzle.create({
|
|
|
|
|
|