import assert from 'node:assert/strict'; import test from 'node:test'; import { combinationSearchLimits, generateAnchoredCombinationCandidates, generateCombinationCandidates, resolveCandidateAwakening, selectMemberDiverseCandidates } from './index.ts'; const cards = [ { id: 'a-1', characterId: 'a', score: 10 }, { id: 'a-2', characterId: 'a', score: 20 }, { id: 'b-1', characterId: 'b', score: 30 }, { id: 'c-1', characterId: 'c', score: 40 } ]; test('고정 카드를 유지하고 같은 멤버의 이격 카드를 제외한다', () => { const result = generateCombinationCandidates([cards[0]], cards, 3, (card) => card.score, 20); assert.equal(result.length, 1); assert.deepEqual(result[0].map((card) => card.id).sort(), ['a-1', 'b-1', 'c-1']); }); test('빔 너비만큼 점수가 높은 조합을 남긴다', () => { const result = generateCombinationCandidates([], cards, 2, (card) => card.score, 2); assert.equal(result.length, 2); assert.deepEqual(result[0].map((card) => card.id).sort(), ['b-1', 'c-1']); }); test('편성 점수로 카드 간 시너지를 선별할 수 있다', () => { const synergyCards = [ { id: 'a-1', characterId: 'a', score: 10, group: 'x' }, { id: 'b-1', characterId: 'b', score: 9, group: 'x' }, { id: 'c-1', characterId: 'c', score: 20, group: 'y' } ]; const result = generateCombinationCandidates( [], synergyCards, 2, (card) => card.score, 2, (selected) => selected.reduce((sum, card) => sum + card.score, 0) + (selected.filter((card) => card.group === 'x').length >= 2 ? 100 : 0) ); assert.deepEqual(result[0].map((card) => card.id).sort(), ['a-1', 'b-1']); }); test('보유한 자동 후보는 설정값보다 실제 개화가 높으면 실제 개화를 사용한다', () => { assert.equal(resolveCandidateAwakening(2, true, 4), 4); assert.equal(resolveCandidateAwakening(4, true, 2), 4); assert.equal(resolveCandidateAwakening(2, false, 5), 2); }); test('빈 멤버 수에 비례해 선별 및 정밀 분석 후보 수를 늘린다', () => { assert.deepEqual(combinationSearchLimits(4), { openSlots: 1, screeningLimit: 5_000, baseExactFinalistLimit: 56, alternativeExactFinalistLimit: 56 }); assert.deepEqual(combinationSearchLimits(3), { openSlots: 2, screeningLimit: 10_000, baseExactFinalistLimit: 112, alternativeExactFinalistLimit: 112 }); assert.deepEqual(combinationSearchLimits(0), { openSlots: 5, screeningLimit: 25_000, baseExactFinalistLimit: 280, alternativeExactFinalistLimit: 280 }); }); test('각 자동 후보를 임시 축으로 삼은 조합을 선별한다', () => { const anchoredCards = [ { id: 'a', characterId: 'a', score: 100 }, { id: 'b', characterId: 'b', score: 90 }, { id: 'c', characterId: 'c', score: 80 }, { id: 'd', characterId: 'd', score: 1 } ]; const result = generateAnchoredCombinationCandidates( [], anchoredCards, 2, [{ scoreCard: (card) => card.score }], 1 ); assert.ok(result.some((combination) => combination.some((card) => card.id === 'd'))); }); test('정밀 분석 후보에 각 카드의 최고 대표 조합을 남긴다', () => { const candidates = [ { cards: [{ id: 'a', characterId: 'a' }, { id: 'b', characterId: 'b' }], score: 100 }, { cards: [{ id: 'a', characterId: 'a' }, { id: 'c', characterId: 'c' }], score: 90 }, { cards: [{ id: 'd', characterId: 'd' }, { id: 'e', characterId: 'e' }], score: 20 } ]; const result = selectMemberDiverseCandidates(candidates, (candidate) => candidate.cards, (candidate) => candidate.score, [], 10); assert.equal(result.length, 3); assert.ok(result.some((candidate) => candidate.cards.some((card) => card.id === 'e'))); });