mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-01 07:37:55 -05:00
Prevent stage early replays in scrim map by map mode
This commit is contained in:
@@ -700,16 +700,15 @@ describe("MapList.resume()", () => {
|
||||
).toBe("SZ");
|
||||
});
|
||||
|
||||
it("exclusion is keyed on (mode, stage), not stage alone", () => {
|
||||
it("avoids the just-played stage when alternatives exist, even across modes", () => {
|
||||
const sharedPool = new MapPool({
|
||||
TW: [],
|
||||
SZ: [1, 2],
|
||||
TC: [1, 2],
|
||||
SZ: [1, 2, 3, 4, 5],
|
||||
TC: [1, 2, 3, 4, 5],
|
||||
RM: [],
|
||||
CB: [],
|
||||
});
|
||||
|
||||
const seenForTC = new Set<StageId>();
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const gen = MapList.resume({
|
||||
mapPool: sharedPool,
|
||||
@@ -718,10 +717,10 @@ describe("MapList.resume()", () => {
|
||||
gen.next();
|
||||
const next = gen.next({ amount: 1 }).value![0];
|
||||
expect(next.mode).toBe("TC");
|
||||
seenForTC.add(next.stageId);
|
||||
expect(
|
||||
next.stageId,
|
||||
"same stage picked back-to-back across modes",
|
||||
).not.toBe(1);
|
||||
}
|
||||
|
||||
expect(seenForTC.has(1)).toBe(true);
|
||||
expect(seenForTC.has(2)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,6 +54,8 @@ export function* generate(args: {
|
||||
skipEnsureMinimumCandidates?: boolean;
|
||||
/** Fixed mode order — when set, skips the random `modeOrders` shuffle and uses only this order. Intended for `resume`. */
|
||||
modeOrder?: ModeShort[];
|
||||
/** Initial weights for stages (mode-agnostic). Used by `resume` to carry over stage-level penalties from history. */
|
||||
initialStageWeights?: Map<StageId, number>;
|
||||
}): Generator<Array<ModeWithStage>, Array<ModeWithStage>, GenerateNext> {
|
||||
if (args.mapPool.isEmpty()) {
|
||||
while (true) yield [];
|
||||
@@ -65,6 +67,7 @@ export function* generate(args: {
|
||||
modes,
|
||||
args.mapPool.parsed,
|
||||
args.initialWeights,
|
||||
args.initialStageWeights,
|
||||
);
|
||||
const orderedModes = args.modeOrder ? [args.modeOrder] : modeOrders(modes);
|
||||
let currentOrderIndex = 0;
|
||||
@@ -164,13 +167,28 @@ export function resume(args: {
|
||||
initialWeights.set(modeStageKey(pair.mode, pair.stageId), 0);
|
||||
}
|
||||
for (const { mode, stageId } of args.history) {
|
||||
initialWeights.set(modeStageKey(mode, stageId), -1000);
|
||||
initialWeights.set(modeStageKey(mode, stageId), -100);
|
||||
}
|
||||
|
||||
const STAGE_PENALTY = -20;
|
||||
const initialStageWeights = new Map<StageId, number>();
|
||||
for (const pair of args.mapPool.stageModePairs) {
|
||||
if (!initialStageWeights.has(pair.stageId)) {
|
||||
initialStageWeights.set(pair.stageId, 0);
|
||||
}
|
||||
}
|
||||
for (const { stageId } of args.history) {
|
||||
for (const [key, value] of initialStageWeights.entries()) {
|
||||
initialStageWeights.set(key, value + 1);
|
||||
}
|
||||
initialStageWeights.set(stageId, STAGE_PENALTY);
|
||||
}
|
||||
|
||||
return generate({
|
||||
mapPool: args.mapPool,
|
||||
modeOrder,
|
||||
initialWeights: initialWeights.size > 0 ? initialWeights : undefined,
|
||||
initialStageWeights,
|
||||
skipEnsureMinimumCandidates: true,
|
||||
});
|
||||
}
|
||||
@@ -179,6 +197,7 @@ function initializeWeights(
|
||||
modes: ModeShort[],
|
||||
mapPool: ReadonlyMapPoolObject,
|
||||
initialWeights?: Map<string, number>,
|
||||
initialStageWeights?: Map<StageId, number>,
|
||||
) {
|
||||
const stageWeights = new Map<StageId, number>();
|
||||
const stageModeWeights = new Map<string, number>();
|
||||
@@ -188,7 +207,7 @@ function initializeWeights(
|
||||
for (const mode of modes) {
|
||||
const stageIds = mapPool[mode];
|
||||
for (const stageId of stageIds) {
|
||||
stageWeights.set(stageId, 0);
|
||||
stageWeights.set(stageId, initialStageWeights?.get(stageId) ?? 0);
|
||||
const key = modeStageKey(mode, stageId);
|
||||
const initialWeight =
|
||||
initialWeights?.get(key) ?? (hasInitialWeights ? -1000 : 0);
|
||||
|
||||
Reference in New Issue
Block a user