返回提交历史
Modified
src/services/rngService.ts
+53
-0
Modified
src/services/worldStateService.ts
+2
-3
XFEstudio/SpaceNinjaServer
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: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2422 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
3d8c1d03
代码差异
2 个文件
+55
-3
@@ -151,4 +151,57 @@ export class SRng {
151
151
arr[lastIdx] = tmp;
152
152
}
153
153
}
154
155
shuffledArray<T>(inarr: readonly T[]): T[] {
156
const arr = [...inarr];
157
this.shuffleArray(arr);
158
return arr;
159
}
154
160
}
161
162
export const sequentiallyUniqueRandomElement = <T>(
163
deck: readonly T[],
164
idx: number,
165
lookbehind: number,
166
seed: number = 0
167
): T | undefined => {
168
// This algorithm may modify a shuffle up to index `lookbehind + 1`. It assumes that the last `lookbehind` cards are not adjusted.
169
if (lookbehind + 1 >= deck.length - lookbehind) {
170
throw new Error(
171
`this algorithm cannot guarantee ${lookbehind} unique cards in a row with a deck of size ${deck.length}`
172
);
173
}
174
175
const iteration = Math.trunc(idx / deck.length);
176
const card = idx % deck.length;
177
const currentShuffle = new SRng(mixSeeds(new SRng(iteration).randomInt(0, 100_000), seed)).shuffledArray(deck);
178
if (card < currentShuffle.length - lookbehind) {
179
// We are indexing before the end of the deck, so adjustments may be needed to achieve uniqueness.
180
const window: T[] = [];
181
{
182
const previousShuffle = new SRng(
183
mixSeeds(new SRng(iteration - 1).randomInt(0, 100_000), seed)
184
).shuffledArray(deck);
185
for (let i = previousShuffle.length - lookbehind; i != previousShuffle.length; ++i) {
186
window.push(previousShuffle[i]);
187
}
188
}
189
// From this point on, `window.length == lookbehind` should hold.
190
for (let i = 0; i != lookbehind; ++i) {
191
if (window.indexOf(currentShuffle[i]) != -1) {
192
for (let j = i; ; ++j) {
193
// `j < currentShuffle.length - lookbehind` should hold.
194
if (window.indexOf(currentShuffle[j]) == -1) {
195
const tmp = currentShuffle[j];
196
currentShuffle[j] = currentShuffle[i];
197
currentShuffle[i] = tmp;
198
break;
199
}
200
}
201
}
202
window.splice(0, 1);
203
window.push(currentShuffle[i]);
204
}
205
}
206
return currentShuffle[card];
207
};
@@ -9,7 +9,7 @@ import darvoDeals from "@/static/fixed_responses/worldState/darvoDeals.json";
9
9
import { buildConfig } from "@/src/services/buildConfigService";
10
10
import { unixTimesInMs } from "@/src/constants/timeConstants";
11
11
import { config } from "@/src/services/configService";
12
import { getRandomElement, getRandomInt, SRng } from "@/src/services/rngService";
12
import { getRandomElement, getRandomInt, sequentiallyUniqueRandomElement, SRng } from "@/src/services/rngService";
13
13
import { eMissionType, ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
14
14
import {
15
15
ICalendarDay,
@@ -385,13 +385,12 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
385
385
const getSeasonDailyChallenge = (pools: IRotatingSeasonChallengePools, day: number): ISeasonChallenge => {
386
386
const dayStart = EPOCH + day * 86400000;
387
387
const dayEnd = EPOCH + (day + 3) * 86400000;
388
const rng = new SRng(new SRng(day).randomInt(0, 100_000));
389
388
return {
390
389
_id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
391
390
Daily: true,
392
391
Activation: { $date: { $numberLong: dayStart.toString() } },
393
392
Expiry: { $date: { $numberLong: dayEnd.toString() } },
394
Challenge: rng.randomElement(pools.daily)!
393
Challenge: sequentiallyUniqueRandomElement(pools.daily, day, 2, 605732938)!
395
394
};
396
395
};
397
396