feat: add green board efficiency analysis
This commit is contained in:
@@ -87,3 +87,34 @@ test('조합 탐색용 기수 보너스와 악곡 보너스는 실제 노드 합
|
||||
assert.equal(zeroOverride.flatByCardId['card-a'].performance, 0);
|
||||
assert.equal(customOverride.flatByCardId['card-a'].performance, 450);
|
||||
});
|
||||
|
||||
test('같은 기수의 보드 보너스는 카드별로 최대 900까지만 적용한다', () => {
|
||||
const cappedSummary: BoardSummarySource = {
|
||||
nodeGroups: {
|
||||
'G-10': [{ characterIds: [], effect: { type: 'all_parameter_up_for_character_grouping', value: 600, targetId: 'skill_tree_effect_target-character_grouping-grp-gen_0' } }],
|
||||
'G-11': [{ characterIds: [], effect: { type: 'all_parameter_up_for_character_grouping', value: 500, targetId: 'skill_tree_effect_target-character_grouping-grp-gen_0' } }]
|
||||
}
|
||||
};
|
||||
|
||||
const result = calculateBoardEffects(cappedSummary, { account: ['G-10', 'G-11'] }, cards, 'a');
|
||||
|
||||
assert.equal(result.flatByCardId['card-a'].performance, 900);
|
||||
assert.equal(result.flatByCardId['card-a'].technique, 900);
|
||||
assert.equal(result.flatByCardId['card-a'].sense, 900);
|
||||
assert.equal(result.flatByCardId['card-b'].performance, 0);
|
||||
});
|
||||
|
||||
test('복수 소속 카드는 각 기수의 900 상한을 따로 적용한다', () => {
|
||||
const multiGroupingSummary: BoardSummarySource = {
|
||||
nodeGroups: {
|
||||
'G-20': [{ characterIds: [], effect: { type: 'all_parameter_up_for_character_grouping', value: 900, targetId: 'skill_tree_effect_target-character_grouping-grp-gen_1' } }],
|
||||
'G-21': [{ characterIds: [], effect: { type: 'all_parameter_up_for_character_grouping', value: 900, targetId: 'skill_tree_effect_target-character_grouping-grp-gamers' } }],
|
||||
'G-22': [{ characterIds: [], effect: { type: 'all_parameter_up_for_character_grouping', value: 100, targetId: 'skill_tree_effect_target-character_grouping-grp-gen_1' } }]
|
||||
}
|
||||
};
|
||||
const multiGroupingCards = [{ id: 'fubuki', characterId: 'fubuki', type: '퓨어', generation: 'hololive 1기생 / GAMERS' }];
|
||||
|
||||
const result = calculateBoardEffects(multiGroupingSummary, { account: ['G-20', 'G-21', 'G-22'] }, multiGroupingCards, 'fubuki');
|
||||
|
||||
assert.equal(result.flatByCardId.fubuki.performance, 1800);
|
||||
});
|
||||
|
||||
@@ -40,6 +40,7 @@ export type BoardEffectsResult = {
|
||||
};
|
||||
|
||||
const stats: readonly UnitStat[] = ['performance', 'technique', 'sense'];
|
||||
const generationBonusCap = 900;
|
||||
const emptyStats = (): Record<UnitStat, number> => ({ performance: 0, technique: 0, sense: 0 });
|
||||
const groupingLabels: Record<string, string> = {
|
||||
'grp-gen_0': '0기생', 'grp-gen_1': '1기생', 'grp-gen_2': '2기생', 'grp-gen_3': '3기생',
|
||||
@@ -140,6 +141,7 @@ export function calculateBoardEffects(
|
||||
let scoreSupportPercent = 0;
|
||||
let directSongScoreBonusPercent = 0;
|
||||
const multipliers = connectionMultipliers(summary, memberBoards, connections);
|
||||
const generationBonusByCardAndGrouping: Record<string, Record<string, number>> = {};
|
||||
|
||||
for (const [ownerId, activeGroupIds] of Object.entries(memberBoards)) {
|
||||
for (const groupId of activeGroupIds) {
|
||||
@@ -172,9 +174,19 @@ export function calculateBoardEffects(
|
||||
|
||||
const destination = effect.type.endsWith('_permil_up') ? rateByCardId : flatByCardId;
|
||||
const normalizedValue = effect.type.endsWith('_permil_up') ? value / 10 : value;
|
||||
const effectTargets = targets(effect, ownerId, cards).filter((card) =>
|
||||
effect.type !== 'all_parameter_up_for_character_grouping' || overrides?.generationBonusByCardId?.[card.id] === undefined
|
||||
);
|
||||
const effectTargets = targets(effect, ownerId, cards);
|
||||
if (effect.type === 'all_parameter_up_for_character_grouping') {
|
||||
const groupingId = effect.targetId ?? 'unknown-grouping';
|
||||
for (const card of effectTargets) {
|
||||
if (overrides?.generationBonusByCardId?.[card.id] !== undefined) continue;
|
||||
const cardBonuses = generationBonusByCardAndGrouping[card.id] ??= {};
|
||||
const currentBonus = cardBonuses[groupingId] ?? 0;
|
||||
const appliedValue = Math.max(0, Math.min(normalizedValue, generationBonusCap - currentBonus));
|
||||
cardBonuses[groupingId] = currentBonus + appliedValue;
|
||||
for (const stat of stats) destination[card.id][stat] += appliedValue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for (const card of effectTargets) {
|
||||
for (const stat of affectedStats(effect.type)) destination[card.id][stat] += normalizedValue;
|
||||
}
|
||||
@@ -183,7 +195,8 @@ export function calculateBoardEffects(
|
||||
|
||||
for (const [cardId, value] of Object.entries(overrides?.generationBonusByCardId ?? {})) {
|
||||
if (!flatByCardId[cardId]) continue;
|
||||
for (const stat of stats) flatByCardId[cardId][stat] += value;
|
||||
const cappedValue = Math.max(0, Math.min(generationBonusCap, value));
|
||||
for (const stat of stats) flatByCardId[cardId][stat] += cappedValue;
|
||||
}
|
||||
|
||||
// 쿨타임 감소는 현재 보드 상태가 아니라 최적화 변수다. 모든 멤버가 네 경우를 항상 탐색한다.
|
||||
|
||||
Reference in New Issue
Block a user