forked from OpenWF/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: OpenWF/SpaceNinjaServer#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