fix: ensure nightwave daily challenges are unique (#2422)
When generating a daily challenge, we now use sequentiallyUniqueRandomElement with a lookbehind of 2 to ensure the 2 previous (and still active) daily challenges are not duplicated. Re #2411 Reviewed-on: #2422 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
parent
773f96ebbc
commit
3d8c1d036a
@ -151,4 +151,57 @@ export class SRng {
|
||||
arr[lastIdx] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
shuffledArray<T>(inarr: readonly T[]): T[] {
|
||||
const arr = [...inarr];
|
||||
this.shuffleArray(arr);
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
export const sequentiallyUniqueRandomElement = <T>(
|
||||
deck: readonly T[],
|
||||
idx: number,
|
||||
lookbehind: number,
|
||||
seed: number = 0
|
||||
): T | undefined => {
|
||||
// This algorithm may modify a shuffle up to index `lookbehind + 1`. It assumes that the last `lookbehind` cards are not adjusted.
|
||||
if (lookbehind + 1 >= deck.length - lookbehind) {
|
||||
throw new Error(
|
||||
`this algorithm cannot guarantee ${lookbehind} unique cards in a row with a deck of size ${deck.length}`
|
||||
);
|
||||
}
|
||||
|
||||
const iteration = Math.trunc(idx / deck.length);
|
||||
const card = idx % deck.length;
|
||||
const currentShuffle = new SRng(mixSeeds(new SRng(iteration).randomInt(0, 100_000), seed)).shuffledArray(deck);
|
||||
if (card < currentShuffle.length - lookbehind) {
|
||||
// We are indexing before the end of the deck, so adjustments may be needed to achieve uniqueness.
|
||||
const window: T[] = [];
|
||||
{
|
||||
const previousShuffle = new SRng(
|
||||
mixSeeds(new SRng(iteration - 1).randomInt(0, 100_000), seed)
|
||||
).shuffledArray(deck);
|
||||
for (let i = previousShuffle.length - lookbehind; i != previousShuffle.length; ++i) {
|
||||
window.push(previousShuffle[i]);
|
||||
}
|
||||
}
|
||||
// From this point on, `window.length == lookbehind` should hold.
|
||||
for (let i = 0; i != lookbehind; ++i) {
|
||||
if (window.indexOf(currentShuffle[i]) != -1) {
|
||||
for (let j = i; ; ++j) {
|
||||
// `j < currentShuffle.length - lookbehind` should hold.
|
||||
if (window.indexOf(currentShuffle[j]) == -1) {
|
||||
const tmp = currentShuffle[j];
|
||||
currentShuffle[j] = currentShuffle[i];
|
||||
currentShuffle[i] = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
window.splice(0, 1);
|
||||
window.push(currentShuffle[i]);
|
||||
}
|
||||
}
|
||||
return currentShuffle[card];
|
||||
};
|
||||
|
@ -9,7 +9,7 @@ import darvoDeals from "@/static/fixed_responses/worldState/darvoDeals.json";
|
||||
import { buildConfig } from "@/src/services/buildConfigService";
|
||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { getRandomElement, getRandomInt, SRng } from "@/src/services/rngService";
|
||||
import { getRandomElement, getRandomInt, sequentiallyUniqueRandomElement, SRng } from "@/src/services/rngService";
|
||||
import { eMissionType, ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
|
||||
import {
|
||||
ICalendarDay,
|
||||
@ -385,13 +385,12 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
|
||||
const getSeasonDailyChallenge = (pools: IRotatingSeasonChallengePools, day: number): ISeasonChallenge => {
|
||||
const dayStart = EPOCH + day * 86400000;
|
||||
const dayEnd = EPOCH + (day + 3) * 86400000;
|
||||
const rng = new SRng(new SRng(day).randomInt(0, 100_000));
|
||||
return {
|
||||
_id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
|
||||
Daily: true,
|
||||
Activation: { $date: { $numberLong: dayStart.toString() } },
|
||||
Expiry: { $date: { $numberLong: dayEnd.toString() } },
|
||||
Challenge: rng.randomElement(pools.daily)!
|
||||
Challenge: sequentiallyUniqueRandomElement(pools.daily, day, 2, 605732938)!
|
||||
};
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user