feat: compare top five cooldown combinations in analysis
Publish GitHub Pages output / publish (push) Successful in 42s
Publish GitHub Pages output / publish (push) Successful in 42s
This commit is contained in:
1 parent
29e70c565d
commit
7c7f0d40c0
5 files changed
+133
-43
No files matched your search
@@ -226,6 +226,16 @@ export function optimizeExactOrderAndCooldowns(
|
||||
chart: ExactSongChart,
|
||||
options: ExactOptimizeOptions
|
||||
): ExactOptimizationResult {
|
||||
return rankExactCooldownCombinations(cards, chart, options, 1)[0];
|
||||
}
|
||||
|
||||
/** Keep the best special order for each distinct per-card cooldown assignment. */
|
||||
export function rankExactCooldownCombinations(
|
||||
cards: readonly OptimizerCard[],
|
||||
chart: ExactSongChart,
|
||||
options: ExactOptimizeOptions,
|
||||
limit = 5
|
||||
): ExactOptimizationResult[] {
|
||||
if (cards.length !== 5 || new Set(cards.map((card) => card.id)).size !== 5) {
|
||||
throw new Error('서로 다른 카드 5장이 필요합니다.');
|
||||
}
|
||||
@@ -234,46 +244,39 @@ export function optimizeExactOrderAndCooldowns(
|
||||
const orderCount = cards.reduce((value, _, index) => value * (index + 1), 1);
|
||||
const totalCandidates = orderCount * reductionAssignments.length;
|
||||
let evaluatedCandidates = 0;
|
||||
let bestTheoreticalMaxScore = Number.NEGATIVE_INFINITY;
|
||||
let bestExpectedScore = Number.NEGATIVE_INFINITY;
|
||||
let bestOrder: OptimizerCard[] | null = null;
|
||||
let bestReductions: Record<string, CooldownReduction> | null = null;
|
||||
let bestReductionTotal = Number.POSITIVE_INFINITY;
|
||||
type Candidate = {
|
||||
order: OptimizerCard[];
|
||||
reductions: Record<string, CooldownReduction>;
|
||||
theoreticalMaxScore: number;
|
||||
expectedScore: number;
|
||||
reductionTotal: number;
|
||||
};
|
||||
const bestByAssignment = new Map<Record<string, CooldownReduction>, Candidate>();
|
||||
const compare = (left: Candidate, right: Candidate): number =>
|
||||
(options.objective === 'expected'
|
||||
? right.expectedScore - left.expectedScore || right.theoreticalMaxScore - left.theoreticalMaxScore
|
||||
: right.theoreticalMaxScore - left.theoreticalMaxScore || right.expectedScore - left.expectedScore) ||
|
||||
left.reductionTotal - right.reductionTotal;
|
||||
|
||||
for (const order of permutations(cards)) {
|
||||
const profiles = makeProfiles(order, chart);
|
||||
for (const reductions of reductionAssignments) {
|
||||
const score = scoreAssignment(order, reductions, profiles, base, options.directSongScoreBonusPercent ?? 0);
|
||||
const reductionTotal = Object.values(reductions).reduce<number>((sum, value) => sum + value, 0);
|
||||
const expectedFirst = options.objective === 'expected';
|
||||
const isBetterPrimary = expectedFirst
|
||||
? score.expectedScore > bestExpectedScore
|
||||
: score.theoreticalMaxScore > bestTheoreticalMaxScore;
|
||||
const isBetterSecondary = expectedFirst
|
||||
? score.expectedScore === bestExpectedScore && score.theoreticalMaxScore > bestTheoreticalMaxScore
|
||||
: score.theoreticalMaxScore === bestTheoreticalMaxScore && score.expectedScore > bestExpectedScore;
|
||||
const isCheaperTie = score.theoreticalMaxScore === bestTheoreticalMaxScore &&
|
||||
score.expectedScore === bestExpectedScore && reductionTotal < bestReductionTotal;
|
||||
if (isBetterPrimary || isBetterSecondary || isCheaperTie) {
|
||||
bestTheoreticalMaxScore = score.theoreticalMaxScore;
|
||||
bestExpectedScore = score.expectedScore;
|
||||
bestOrder = order;
|
||||
bestReductions = reductions;
|
||||
bestReductionTotal = reductionTotal;
|
||||
}
|
||||
const candidate = { ...score, order, reductions, reductionTotal };
|
||||
const previous = bestByAssignment.get(reductions);
|
||||
if (!previous || compare(candidate, previous) < 0) bestByAssignment.set(reductions, candidate);
|
||||
evaluatedCandidates += 1;
|
||||
}
|
||||
options.onProgress?.({ evaluatedCandidates, totalCandidates });
|
||||
}
|
||||
|
||||
if (!bestOrder || !bestReductions) throw new Error('분석 결과를 만들지 못했습니다.');
|
||||
const windows = buildExactSkillWindows(bestOrder, bestReductions, chart);
|
||||
return {
|
||||
...calculateExactScore(bestOrder, bestReductions, chart, options),
|
||||
order: bestOrder.map((card) => card.id),
|
||||
return [...bestByAssignment.values()].sort(compare).slice(0, Math.max(1, Math.floor(limit))).map((candidate) => ({
|
||||
...calculateExactScore(candidate.order, candidate.reductions, chart, options),
|
||||
order: candidate.order.map((card) => card.id),
|
||||
formationOrder: cards.map((card) => card.id),
|
||||
cooldownReductions: { ...bestReductions },
|
||||
...windows,
|
||||
cooldownReductions: { ...candidate.reductions },
|
||||
...buildExactSkillWindows(candidate.order, candidate.reductions, chart),
|
||||
evaluatedCandidates
|
||||
};
|
||||
}));
|
||||
}
|
||||
Reference in new issue
Block a user