chore: ensure nightwave daily challenges are unique
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Build / build (pull_request) Successful in 49s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Build / build (pull_request) Successful in 49s
				
			When generating a daily challenge, we now use sequentallyUniqueRandomElement with a lookbehind of 2 to ensure the 2 previous (and still active) daily challenges are not duplicated.
This commit is contained in:
		
							parent
							
								
									2a80307c26
								
							
						
					
					
						commit
						c275e7f9fc
					
				@ -151,4 +151,57 @@ export class SRng {
 | 
				
			|||||||
            arr[lastIdx] = tmp;
 | 
					            arr[lastIdx] = tmp;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    shuffledArray<T>(inarr: readonly T[]): T[] {
 | 
				
			||||||
 | 
					        const arr = [...inarr];
 | 
				
			||||||
 | 
					        this.shuffleArray(arr);
 | 
				
			||||||
 | 
					        return arr;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const sequentallyUniqueRandomElement = <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 { buildConfig } from "@/src/services/buildConfigService";
 | 
				
			||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
					import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
				
			||||||
import { config } from "@/src/services/configService";
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
import { getRandomElement, getRandomInt, SRng } from "@/src/services/rngService";
 | 
					import { getRandomElement, getRandomInt, sequentallyUniqueRandomElement, SRng } from "@/src/services/rngService";
 | 
				
			||||||
import { eMissionType, ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
 | 
					import { eMissionType, ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    ICalendarDay,
 | 
					    ICalendarDay,
 | 
				
			||||||
@ -385,13 +385,12 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
 | 
				
			|||||||
const getSeasonDailyChallenge = (pools: IRotatingSeasonChallengePools, day: number): ISeasonChallenge => {
 | 
					const getSeasonDailyChallenge = (pools: IRotatingSeasonChallengePools, day: number): ISeasonChallenge => {
 | 
				
			||||||
    const dayStart = EPOCH + day * 86400000;
 | 
					    const dayStart = EPOCH + day * 86400000;
 | 
				
			||||||
    const dayEnd = EPOCH + (day + 3) * 86400000;
 | 
					    const dayEnd = EPOCH + (day + 3) * 86400000;
 | 
				
			||||||
    const rng = new SRng(new SRng(day).randomInt(0, 100_000));
 | 
					 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        _id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
 | 
					        _id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
 | 
				
			||||||
        Daily: true,
 | 
					        Daily: true,
 | 
				
			||||||
        Activation: { $date: { $numberLong: dayStart.toString() } },
 | 
					        Activation: { $date: { $numberLong: dayStart.toString() } },
 | 
				
			||||||
        Expiry: { $date: { $numberLong: dayEnd.toString() } },
 | 
					        Expiry: { $date: { $numberLong: dayEnd.toString() } },
 | 
				
			||||||
        Challenge: rng.randomElement(pools.daily)!
 | 
					        Challenge: sequentallyUniqueRandomElement(pools.daily, day, 2, 605732938)!
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user