feat: add combination search workflow

This commit is contained in:
2026-08-18 16:30:51 +09:00
parent 7b713bffe3
commit f953fe9970
10 changed files with 1294 additions and 24 deletions
+51 -11
View File
@@ -1,7 +1,7 @@
/// <reference lib="webworker" />
import { optimizeExactOrderAndCooldowns } from '../engine/index.ts';
import type { ExactOptimizeOptions, ExactSongChart, OptimizerCard } from '../engine/index.ts';
import { countOptimizationCandidates, optimizeExactOrderAndCooldowns } from '../engine/index.ts';
import type { ExactOptimizationResult, ExactOptimizeOptions, ExactSongChart, OptimizerCard } from '../engine/index.ts';
type OptimizeRequest = {
type: 'optimize-exact';
@@ -11,17 +11,57 @@ type OptimizeRequest = {
directSongScoreBonusPercent?: number;
};
self.onmessage = (event: MessageEvent<OptimizeRequest>) => {
if (event.data.type !== 'optimize-exact') return;
type CombinationCandidate = {
cards: OptimizerCard[];
overallPower: number;
perfectNoteScore: number;
directSongScoreBonusPercent?: number;
};
type CombinationRequest = {
type: 'optimize-combinations';
candidates: CombinationCandidate[];
chart: ExactSongChart;
};
self.onmessage = (event: MessageEvent<OptimizeRequest | CombinationRequest>) => {
try {
const options: ExactOptimizeOptions = {
perfectNoteScore: event.data.perfectNoteScore,
directSongScoreBonusPercent: event.data.directSongScoreBonusPercent,
onProgress: (progress) => self.postMessage({ type: 'progress', progress })
};
const result = optimizeExactOrderAndCooldowns(event.data.cards, event.data.chart, options);
self.postMessage({ type: 'result', result });
if (event.data.type === 'optimize-exact') {
const options: ExactOptimizeOptions = {
perfectNoteScore: event.data.perfectNoteScore,
directSongScoreBonusPercent: event.data.directSongScoreBonusPercent,
onProgress: (progress) => self.postMessage({ type: 'progress', progress })
};
const result = optimizeExactOrderAndCooldowns(event.data.cards, event.data.chart, options);
self.postMessage({ type: 'result', result });
return;
}
const totalCandidates = event.data.candidates.reduce((sum, candidate) => sum + countOptimizationCandidates(candidate.cards), 0);
let completedCandidates = 0;
let best: { result: ExactOptimizationResult; overallPower: number } | null = null;
const rankings: { result: ExactOptimizationResult; overallPower: number }[] = [];
for (const candidate of event.data.candidates) {
const result = optimizeExactOrderAndCooldowns(candidate.cards, event.data.chart, {
perfectNoteScore: candidate.perfectNoteScore,
directSongScoreBonusPercent: candidate.directSongScoreBonusPercent,
onProgress: (progress) => self.postMessage({
type: 'progress',
progress: { evaluatedCandidates: completedCandidates + progress.evaluatedCandidates, totalCandidates }
})
});
completedCandidates += countOptimizationCandidates(candidate.cards);
if (!best || result.theoreticalMaxScore > best.result.theoreticalMaxScore ||
(result.theoreticalMaxScore === best.result.theoreticalMaxScore && result.expectedScore > best.result.expectedScore)) {
best = { result, overallPower: candidate.overallPower };
}
rankings.push({ result, overallPower: candidate.overallPower });
}
if (!best) throw new Error('정밀 분석할 조합이 없습니다.');
rankings.sort((left, right) => right.result.theoreticalMaxScore - left.result.theoreticalMaxScore ||
right.result.expectedScore - left.result.expectedScore);
self.postMessage({ type: 'combination-result', ...best, rankings: rankings.slice(0, 10) });
} catch (error) {
self.postMessage({
type: 'error',