카드 조건과 빈도별 공통 계산을 재사용하고 준비·평가 Worker를 재사용합니다. 준비된 후보를 모두 정밀 평가하고 최종 결과를 한 번 표시합니다. 업데이트 문구를 한 항목으로 통합합니다.
This commit is contained in:
1 parent
f009a7fd6b
commit
da40ec4238
18 files changed
+1623
-467
No files matched your search
@@ -6,6 +6,10 @@ export type CombinationSearchCard = {
|
||||
type BeamState<T extends CombinationSearchCard> = {
|
||||
cards: T[];
|
||||
score: number;
|
||||
mask0: number;
|
||||
mask1: number;
|
||||
mask2: number;
|
||||
tieKey?: string;
|
||||
};
|
||||
|
||||
export type CombinationSearchLimits = {
|
||||
@@ -70,7 +74,18 @@ export function generateCombinationCandidates<T extends CombinationSearchCard>(
|
||||
const fixedIds = new Set(fixedCards.map((card) => card.id));
|
||||
const fixedCharacters = new Set(fixedCards.map((card) => card.characterId));
|
||||
const pool = candidateCards.filter((card) => !fixedIds.has(card.id) && !fixedCharacters.has(card.characterId));
|
||||
const identities = [...new Set([...fixedCards, ...pool].map((card) => card.id))];
|
||||
const compactKeys = identities.length <= 96 && identities.every((id) => typeof id === 'string' && !id.includes('|'));
|
||||
const flags = new Map(identities.map((id, index) => [id, [
|
||||
compactKeys && index < 32 ? 1 << index : 0,
|
||||
compactKeys && index >= 32 && index < 64 ? 1 << (index - 32) : 0,
|
||||
compactKeys && index >= 64 ? 1 << (index - 64) : 0
|
||||
]]));
|
||||
const entries = pool.map((card) => ({ card, flags: flags.get(card.id)! }));
|
||||
const fixedMasks = fixedCards.reduce((mask, card) => mask.map((value, index) => value | flags.get(card.id)![index]), [0, 0, 0]);
|
||||
const tieKey = (state: BeamState<T>): string => state.tieKey ??= state.cards.map((card) => card.id).join();
|
||||
let beam: BeamState<T>[] = [{
|
||||
mask0: fixedMasks[0], mask1: fixedMasks[1], mask2: fixedMasks[2], tieKey: undefined,
|
||||
cards: Array.from(fixedCards),
|
||||
score: scoreCombination?.(fixedCards) ?? fixedCards.reduce((sum, card) => sum + scoreCard(card), 0)
|
||||
}];
|
||||
@@ -80,21 +95,28 @@ export function generateCombinationCandidates<T extends CombinationSearchCard>(
|
||||
for (const state of beam) {
|
||||
const usedCharacters = new Set(state.cards.map((card) => card.characterId));
|
||||
const usedIds = new Set(state.cards.map((card) => card.id));
|
||||
for (const card of pool) {
|
||||
for (const { card, flags: bits } of entries) {
|
||||
if (usedIds.has(card.id)) continue;
|
||||
if (usedCharacters.has(card.characterId)) continue;
|
||||
const mask0 = state.mask0 | bits[0], mask1 = state.mask1 | bits[1], mask2 = state.mask2 | bits[2];
|
||||
const compactKey = compactKeys ? mask0 + ':' + mask1 + ':' + mask2 : '';
|
||||
if (compactKeys && !acceptsCombination && expanded.has(compactKey)) continue;
|
||||
const cards = [...state.cards, card];
|
||||
if (acceptsCombination && !acceptsCombination(cards)) continue;
|
||||
// Preserve the first traversal's card order and Map insertion order.
|
||||
// Scoring callbacks are pure; duplicate permutations need no evaluation.
|
||||
const key = compactKeys ? compactKey : cards.map((entry) => entry.id).sort().join('|');
|
||||
if (expanded.has(key)) continue;
|
||||
const nextState = {
|
||||
mask0, mask1, mask2, tieKey: undefined,
|
||||
cards,
|
||||
score: scoreCombination?.(cards) ?? state.score + scoreCard(card)
|
||||
};
|
||||
const key = nextState.cards.map((entry) => entry.id).sort().join('|');
|
||||
if (!expanded.has(key)) expanded.set(key, nextState);
|
||||
expanded.set(key, nextState);
|
||||
}
|
||||
}
|
||||
beam = Array.from(expanded.values())
|
||||
.sort((left, right) => right.score - left.score || left.cards.map((card) => card.id).join().localeCompare(right.cards.map((card) => card.id).join()))
|
||||
.sort((left, right) => right.score - left.score || tieKey(left).localeCompare(tieKey(right)))
|
||||
.slice(0, Math.max(1, beamWidth));
|
||||
if (beam.length === 0) break;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user