[Bug] Fix evil team admin randomization (#6830)

This commit is contained in:
Sirz Benjie 2025-12-18 21:41:32 -06:00 committed by GitHub
parent 8f4243853d
commit e8b1d0fd71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 58 additions and 14 deletions

View File

@ -12,7 +12,7 @@
import { globalScene } from "#app/global-scene";
import type { Mutable } from "#types/type-helpers";
import { randSeedItem } from "#utils/common";
import { randSeedItem, randSeedShuffle } from "#utils/common";
/**
* Select a random element using an offset such that the chosen element is
@ -21,9 +21,10 @@ import { randSeedItem } from "#utils/common";
* @remarks
* If the seed offset is greater than the number of choices, this will just choose a random element
*
* @param arr - The array of items to choose from
* @param choices - The array of items to choose from
* @param seedOffset - The offset into the array
* @param scene - (default {@linkcode globalScene}); The scene to use for random seeding
* @returns A random item from the array that is guaranteed to be different from the
* @returns A random item from the array that is guaranteed to be different from the previous result
* @typeParam T - The type of items in the array
*
* @example
@ -38,8 +39,7 @@ import { randSeedItem } from "#utils/common";
* ```
*/
export function randSeedUniqueItem<T>(choices: readonly T[], seedOffset: number, scene = globalScene): T {
if (seedOffset === 0 || choices.length <= seedOffset) {
// cast to mutable is safe because randSeedItem does not actually modify the array
if (choices.length <= seedOffset) {
return randSeedItem(choices as Mutable<typeof choices>);
}
@ -47,14 +47,7 @@ export function randSeedUniqueItem<T>(choices: readonly T[], seedOffset: number,
let choice: T;
scene.executeWithSeedOffset(() => {
const curChoices = choices.slice();
for (let i = 0; i < seedOffset; i++) {
const previousChoice = randSeedItem(curChoices);
curChoices.splice(curChoices.indexOf(previousChoice), 1);
}
choice = randSeedItem(curChoices);
}, seedOffset);
// Bang is safe since there are at least `seedOffset` choices, so the method above is guaranteed to set `choice`
choice = randSeedShuffle(choices.slice())[seedOffset];
}, 0);
return choice!;
}

51
test/utils/random.test.ts Normal file
View File

@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Pagefault Games
* SPDX-FileContributor: SirzBenjie
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { GameManager } from "#test/test-utils/game-manager";
import { randSeedUniqueItem } from "#utils/random";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Utils - Random", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
describe("randSeedUniqueItem", () => {
// TODO: Remove `initialization of game` once `randSeedUniqueItem` stops using `executeWithSeedOffset`
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
});
it("should prevent duplicates when provided with different offsets", async () => {
const choices = ["a", "b", "c", "d"];
const choice1 = randSeedUniqueItem(choices, 0);
const choice2 = randSeedUniqueItem(choices, 1);
const choice3 = randSeedUniqueItem(choices, 2);
expect(choice2).not.toEqual(choice1);
expect(choice2).not.toEqual(choice3);
expect(choice1).not.toEqual(choice3);
});
it("should gracefully handle an offset larger than the choices", () => {
const choices = ["a", "b", "c"];
// 1) the function must not throw
// 2) The output must be one of the choices
const choice = randSeedUniqueItem(choices, 5);
expect(choices).toContain(choice);
});
});
});