154 lines
6.6 KiB
TypeScript
154 lines
6.6 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
combinationSearchLimits,
|
|
generateAnchoredCombinationCandidates,
|
|
generateCombinationCandidates,
|
|
resolveCandidateAwakening,
|
|
selectMemberDiverseCandidates,
|
|
withinMediumProbabilityCardLimit
|
|
} 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 }
|
|
];
|
|
|
|
const probabilityCards = [46, 46, 55, 55, 55, 55].map((probabilityPercent, index) => ({
|
|
id: String(index),
|
|
characterId: String(index),
|
|
score: 100 - index,
|
|
details: { active: [{ probabilityPercent }, { probabilityPercent }] }
|
|
}));
|
|
|
|
test('중간 확률은 스킬 레벨 수와 무관하게 카드당 한 장으로 세고 낮음은 제외한다', () => {
|
|
assert.equal(withinMediumProbabilityCardLimit([probabilityCards[0]], 1), true);
|
|
assert.equal(withinMediumProbabilityCardLimit([probabilityCards[0]], 0), false);
|
|
assert.equal(withinMediumProbabilityCardLimit([
|
|
probabilityCards[2],
|
|
{ details: { active: [{ probabilityPercent: 37 }] } },
|
|
{ details: { active: [] } }
|
|
], 0), true);
|
|
assert.equal(withinMediumProbabilityCardLimit(probabilityCards, null), true);
|
|
});
|
|
|
|
test('높은 점수의 중간 확률 카드가 빔을 독점하기 전에 제한을 적용한다', () => {
|
|
const result = generateCombinationCandidates([], probabilityCards, 5, (card) => card.score, 1,
|
|
undefined, (selected) => withinMediumProbabilityCardLimit(selected, 1));
|
|
assert.equal(result.length, 1);
|
|
assert.deepEqual(result[0].map((card) => card.id).sort(), ['0', '2', '3', '4', '5']);
|
|
});
|
|
|
|
test('고정 카드도 중간 확률 제한에 포함하고 초과하거나 완성할 수 없으면 후보를 반환하지 않는다', () => {
|
|
const accepts = (selected: readonly (typeof probabilityCards)[number][]) => withinMediumProbabilityCardLimit(selected, 1);
|
|
const result = generateCombinationCandidates([probabilityCards[1]], probabilityCards, 5, (card) => card.score, 10, undefined, accepts);
|
|
assert.equal(result.length, 1);
|
|
assert.ok(result[0].some((card) => card.id === '1'));
|
|
assert.ok(result[0].every((card) => card.id !== '0'));
|
|
assert.deepEqual(generateCombinationCandidates(probabilityCards.slice(0, 2), probabilityCards, 5,
|
|
(card) => card.score, 10, undefined, accepts), []);
|
|
assert.deepEqual(generateCombinationCandidates([], probabilityCards, 5,
|
|
(card) => card.score, 10, undefined, (selected) => withinMediumProbabilityCardLimit(selected, 0)), []);
|
|
});
|
|
|
|
test('임시 축 후보에도 제한을 적용하고 중간 확률 카드를 모두 제외할 수 있다', () => {
|
|
const result = generateAnchoredCombinationCandidates([], probabilityCards, 3,
|
|
[{ scoreCard: (card) => card.score }], 1,
|
|
(selected) => withinMediumProbabilityCardLimit(selected, 0));
|
|
assert.ok(result.length > 0);
|
|
assert.ok(result.every((selected) => selected.every((card) => card.details.active[0].probabilityPercent === 55)));
|
|
});
|
|
|
|
test('제한 없음은 기존 조합 후보를 그대로 유지한다', () => {
|
|
const expected = generateCombinationCandidates([], probabilityCards, 5, (card) => card.score, 10);
|
|
assert.deepEqual(generateCombinationCandidates([], probabilityCards, 5, (card) => card.score, 10,
|
|
undefined, (selected) => withinMediumProbabilityCardLimit(selected, null)), expected);
|
|
});
|
|
|
|
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')));
|
|
});
|