고정 멤버와 탐색 목적을 유지하며 제한적인 두 카드 교체 후보를 정밀 평가합니다. 기존 평가 예산 안에서 이미 평가한 편성을 제외하고 후보를 재선택합니다.
This commit is contained in:
1 parent
da40ec4238
commit
1e893e62c9
7 files changed
+330
-8
No files matched your search
@@ -0,0 +1,83 @@
|
||||
import { effectiveActiveProbability } from './active-probability.ts';
|
||||
import type { CombinationSearchCard } from './combination-search.ts';
|
||||
import type { OptimizerCard } from './types.ts';
|
||||
|
||||
type RefinementCandidate = {
|
||||
cards: readonly OptimizerCard[];
|
||||
overallPower: number;
|
||||
optimizationObjective?: 'maximum' | 'expected';
|
||||
directSongScoreBonusPercent?: number;
|
||||
finalScoreBonusPercent?: number;
|
||||
rankingMultiplier?: number;
|
||||
};
|
||||
|
||||
export function refinementCandidatePriority(candidate: RefinementCandidate): number {
|
||||
const expected = candidate.optimizationObjective === 'expected';
|
||||
const skillPotential = candidate.cards.reduce((sum, card) => sum +
|
||||
card.active.scoreUpPercent * card.active.durationSeconds / Math.max(1, card.active.cooldownSeconds) *
|
||||
(expected ? effectiveActiveProbability(card) : 1) +
|
||||
card.special.scoreUpPercent * card.special.durationSeconds / 100 +
|
||||
(card.scoreSupportPercent ?? 0) +
|
||||
(card.passiveScoreSupportEffects ?? []).reduce((total, effect) => total +
|
||||
effect.percent * (effect.target === 'all' ? candidate.cards.length : effect.targetCount ?? 1), 0), 0);
|
||||
return candidate.overallPower * (1 + skillPotential / 500) *
|
||||
(1 + (candidate.directSongScoreBonusPercent ?? 0) / 100) *
|
||||
(1 + (candidate.finalScoreBonusPercent ?? 0) / 100) * (candidate.rankingMultiplier ?? 1);
|
||||
}
|
||||
|
||||
export function refinementCombinationKey(cards: readonly { id: string }[]): string {
|
||||
return cards.map((card) => card.id).sort().join('|');
|
||||
}
|
||||
|
||||
/** Bounded two-card neighborhoods; all final scores still use the exact evaluator. */
|
||||
export function generateTwoCardReplacementCandidates<T extends CombinationSearchCard, P>(
|
||||
sources: readonly (readonly T[])[],
|
||||
candidates: readonly T[],
|
||||
fixedIds: ReadonlySet<string>,
|
||||
prepare: (cards: T[]) => P,
|
||||
priority: (candidate: P) => number,
|
||||
accepts: (cards: readonly T[]) => boolean
|
||||
): P[] {
|
||||
const unique = new Map<string, P>();
|
||||
const valid = (cards: readonly T[]) => new Set(cards.map((card) => card.characterId)).size === cards.length && accepts(cards);
|
||||
for (const source of sources.slice(0, 3)) {
|
||||
if (source.length !== 5 || !valid(source)) continue;
|
||||
for (let first = 0; first < 5; first++) {
|
||||
if (fixedIds.has(source[first].id)) continue;
|
||||
for (let second = first + 1; second < 5; second++) {
|
||||
if (fixedIds.has(source[second].id)) continue;
|
||||
const kept = source.filter((_, index) => index !== first && index !== second);
|
||||
const shortlist = candidates
|
||||
.filter((card) => valid([...kept, card]))
|
||||
.map((card) => ({ card, score: priority(prepare([...kept, card])) }))
|
||||
.sort((left, right) => right.score - left.score || left.card.id.localeCompare(right.card.id))
|
||||
.slice(0, 8);
|
||||
for (let left = 0; left < shortlist.length; left++) {
|
||||
for (let right = left + 1; right < shortlist.length; right++) {
|
||||
const cards = [...kept, shortlist[left].card, shortlist[right].card];
|
||||
if (!valid(cards)) continue;
|
||||
const key = refinementCombinationKey(cards);
|
||||
if (!unique.has(key)) unique.set(key, prepare(cards));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(unique.values());
|
||||
}
|
||||
|
||||
export function selectRefinementCandidates<T extends RefinementCandidate>(
|
||||
candidates: readonly T[],
|
||||
evaluatedKeys: ReadonlySet<string>,
|
||||
limit: number
|
||||
): T[] {
|
||||
const unique = new Map<string, T>();
|
||||
for (const candidate of candidates) {
|
||||
const key = refinementCombinationKey(candidate.cards);
|
||||
if (!evaluatedKeys.has(key) && !unique.has(key)) unique.set(key, candidate);
|
||||
}
|
||||
return Array.from(unique.values())
|
||||
.sort((left, right) => refinementCandidatePriority(right) - refinementCandidatePriority(left) ||
|
||||
refinementCombinationKey(left.cards).localeCompare(refinementCombinationKey(right.cards)))
|
||||
.slice(0, Math.max(0, limit));
|
||||
}
|
||||
Reference in new issue
Block a user