test: GCD 시뮬레이션 테스트 추가
- 다양한 GCD 값(0~3000ms)에 대한 전투 효율성 비교 - 레벨별 GCD 영향 분석 테스트 - DPS, 스킬 사용 빈도, 전투 시간 측정 - 권장 GCD 분석 결과: 1500~2000ms
This commit is contained in:
260
test/core/engine/gcd_simulation_test.dart
Normal file
260
test/core/engine/gcd_simulation_test.dart
Normal file
@@ -0,0 +1,260 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
/// 글로벌 쿨타임(GCD) 시뮬레이션 테스트
|
||||
///
|
||||
/// 다양한 GCD 값에 대해 전투 효율성을 측정
|
||||
void main() {
|
||||
test('GCD 시뮬레이션 - 다양한 값 비교', () {
|
||||
print('\n' + '=' * 80);
|
||||
print('글로벌 쿨타임(GCD) 시뮬레이션 결과');
|
||||
print('=' * 80);
|
||||
|
||||
// 테스트할 GCD 값들 (ms)
|
||||
final gcdValues = [0, 500, 1000, 1500, 2000, 2500, 3000];
|
||||
|
||||
// 시뮬레이션 파라미터
|
||||
const playerAttackDelay = 1000; // 플레이어 기본 공격 딜레이
|
||||
const monsterAttackDelay = 1200; // 몬스터 공격 딜레이
|
||||
const skillUseProbability = 0.30; // 스킬 사용 확률 (30%)
|
||||
const normalAttackDamage = 50; // 일반 공격 데미지
|
||||
const skillDamage = 120; // 스킬 데미지 (평균)
|
||||
const monsterHp = 1000; // 몬스터 HP
|
||||
const simulationRuns = 1000; // 시뮬레이션 반복 횟수
|
||||
|
||||
final results = <int, SimulationResult>{};
|
||||
|
||||
for (final gcd in gcdValues) {
|
||||
final result = _runSimulation(
|
||||
gcd: gcd,
|
||||
playerAttackDelay: playerAttackDelay,
|
||||
monsterAttackDelay: monsterAttackDelay,
|
||||
skillUseProbability: skillUseProbability,
|
||||
normalAttackDamage: normalAttackDamage,
|
||||
skillDamage: skillDamage,
|
||||
monsterHp: monsterHp,
|
||||
runs: simulationRuns,
|
||||
);
|
||||
results[gcd] = result;
|
||||
}
|
||||
|
||||
// 결과 출력
|
||||
print('\n### 시뮬레이션 조건');
|
||||
print('- 플레이어 공격 딜레이: ${playerAttackDelay}ms');
|
||||
print('- 몬스터 HP: $monsterHp');
|
||||
print('- 일반 공격 데미지: $normalAttackDamage');
|
||||
print('- 스킬 데미지 (평균): $skillDamage');
|
||||
print('- 스킬 사용 확률: ${(skillUseProbability * 100).toInt()}%');
|
||||
print('- 시뮬레이션 횟수: $simulationRuns회');
|
||||
|
||||
print('\n### 결과 비교');
|
||||
print('| GCD (ms) | 전투시간 (ms) | 총 공격 | 스킬 사용 | 스킬비율 | DPS | 효율 |');
|
||||
print(
|
||||
'|----------|---------------|---------|-----------|----------|-----|------|',
|
||||
);
|
||||
|
||||
final baselineDps = results[0]!.avgDps;
|
||||
for (final gcd in gcdValues) {
|
||||
final r = results[gcd]!;
|
||||
final efficiency = (r.avgDps / baselineDps * 100).toStringAsFixed(0);
|
||||
print(
|
||||
'| ${gcd.toString().padLeft(8)} '
|
||||
'| ${r.avgCombatTime.toStringAsFixed(0).padLeft(13)} '
|
||||
'| ${r.avgTotalAttacks.toStringAsFixed(1).padLeft(7)} '
|
||||
'| ${r.avgSkillUses.toStringAsFixed(1).padLeft(9)} '
|
||||
'| ${(r.skillRatio * 100).toStringAsFixed(1).padLeft(7)}% '
|
||||
'| ${r.avgDps.toStringAsFixed(1).padLeft(3)} '
|
||||
'| ${efficiency.padLeft(4)}% |',
|
||||
);
|
||||
}
|
||||
|
||||
// 권장 GCD 분석
|
||||
print('\n### 분석 및 권장');
|
||||
|
||||
// GCD 1500ms 기준 분석
|
||||
final gcd1500 = results[1500]!;
|
||||
final gcd2000 = results[2000]!;
|
||||
// gcd1000은 비교용으로 추후 활용 가능
|
||||
|
||||
print('\n#### GCD별 특징');
|
||||
print('- **0ms (없음)**: 기준선, 스킬 남용 가능');
|
||||
print('- **500ms**: 거의 제한 없음, 빠른 연속 스킬 가능');
|
||||
print('- **1000ms**: 공격 딜레이와 동일, 매 공격마다 스킬 선택 가능');
|
||||
print('- **1500ms**: 스킬 후 1회 일반공격 강제, 적절한 제한');
|
||||
print('- **2000ms**: 스킬 후 2회 일반공격 강제, 스킬이 특별해짐');
|
||||
print('- **2500ms+**: 스킬 사용이 매우 제한적, 전략적 선택 필요');
|
||||
|
||||
print('\n#### 권장 GCD');
|
||||
print('**1500ms ~ 2000ms 권장**');
|
||||
print('- 스킬 사용 빈도가 자연스럽게 제한됨');
|
||||
print('- 일반 공격의 중요성이 유지됨');
|
||||
print(
|
||||
'- DPS 손실이 크지 않음 (${((1 - gcd1500.avgDps / baselineDps) * 100).toStringAsFixed(0)}% ~ ${((1 - gcd2000.avgDps / baselineDps) * 100).toStringAsFixed(0)}%)',
|
||||
);
|
||||
|
||||
print('\n' + '=' * 80);
|
||||
|
||||
// 테스트는 항상 통과 (정보 출력용)
|
||||
expect(true, isTrue);
|
||||
});
|
||||
|
||||
test('GCD 상세 시뮬레이션 - 레벨별 영향', () {
|
||||
print('\n' + '=' * 80);
|
||||
print('레벨별 GCD 영향 분석');
|
||||
print('=' * 80);
|
||||
|
||||
// 레벨별 파라미터
|
||||
final levelConfigs = [
|
||||
{'level': 1, 'playerAtk': 30, 'skillDmg': 80, 'monsterHp': 200},
|
||||
{'level': 10, 'playerAtk': 50, 'skillDmg': 130, 'monsterHp': 500},
|
||||
{'level': 30, 'playerAtk': 90, 'skillDmg': 220, 'monsterHp': 1500},
|
||||
{'level': 50, 'playerAtk': 130, 'skillDmg': 320, 'monsterHp': 3000},
|
||||
];
|
||||
|
||||
const gcdToTest = [0, 1500, 2000];
|
||||
|
||||
print('\n| 레벨 | GCD | 전투시간 | 스킬횟수 | DPS | 효율 |');
|
||||
print('|------|-----|----------|----------|-----|------|');
|
||||
|
||||
for (final config in levelConfigs) {
|
||||
final level = config['level'] as int;
|
||||
final playerAtk = config['playerAtk'] as int;
|
||||
final skillDmg = config['skillDmg'] as int;
|
||||
final monsterHp = config['monsterHp'] as int;
|
||||
|
||||
double? baselineDps;
|
||||
|
||||
for (final gcd in gcdToTest) {
|
||||
final result = _runSimulation(
|
||||
gcd: gcd,
|
||||
playerAttackDelay: 1000,
|
||||
monsterAttackDelay: 1200,
|
||||
skillUseProbability: 0.30,
|
||||
normalAttackDamage: playerAtk,
|
||||
skillDamage: skillDmg,
|
||||
monsterHp: monsterHp,
|
||||
runs: 500,
|
||||
);
|
||||
|
||||
baselineDps ??= result.avgDps;
|
||||
final efficiency = (result.avgDps / baselineDps * 100).toStringAsFixed(
|
||||
0,
|
||||
);
|
||||
|
||||
print(
|
||||
'| ${level.toString().padLeft(4)} '
|
||||
'| ${gcd.toString().padLeft(3)} '
|
||||
'| ${result.avgCombatTime.toStringAsFixed(0).padLeft(8)} '
|
||||
'| ${result.avgSkillUses.toStringAsFixed(1).padLeft(8)} '
|
||||
'| ${result.avgDps.toStringAsFixed(1).padLeft(3)} '
|
||||
'| ${efficiency.padLeft(4)}% |',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
print('\n' + '=' * 80);
|
||||
expect(true, isTrue);
|
||||
});
|
||||
}
|
||||
|
||||
/// 시뮬레이션 실행
|
||||
SimulationResult _runSimulation({
|
||||
required int gcd,
|
||||
required int playerAttackDelay,
|
||||
required int monsterAttackDelay,
|
||||
required double skillUseProbability,
|
||||
required int normalAttackDamage,
|
||||
required int skillDamage,
|
||||
required int monsterHp,
|
||||
required int runs,
|
||||
}) {
|
||||
final random = Random(42); // 재현 가능한 시드
|
||||
|
||||
var totalCombatTime = 0;
|
||||
var totalAttacks = 0;
|
||||
var totalSkillUses = 0;
|
||||
var totalDamage = 0;
|
||||
|
||||
for (var i = 0; i < runs; i++) {
|
||||
var currentHp = monsterHp;
|
||||
var elapsedTime = 0;
|
||||
var attackCount = 0;
|
||||
var skillCount = 0;
|
||||
var gcdRemaining = 0; // 남은 GCD 시간
|
||||
|
||||
var playerAccum = 0;
|
||||
|
||||
while (currentHp > 0) {
|
||||
// 시간 진행 (100ms 단위)
|
||||
const tickMs = 100;
|
||||
elapsedTime += tickMs;
|
||||
playerAccum += tickMs;
|
||||
|
||||
// GCD 감소
|
||||
if (gcdRemaining > 0) {
|
||||
gcdRemaining -= tickMs;
|
||||
if (gcdRemaining < 0) gcdRemaining = 0;
|
||||
}
|
||||
|
||||
// 플레이어 공격 체크
|
||||
if (playerAccum >= playerAttackDelay) {
|
||||
playerAccum -= playerAttackDelay;
|
||||
attackCount++;
|
||||
|
||||
// 스킬 사용 여부 결정
|
||||
final canUseSkill = gcdRemaining <= 0;
|
||||
final wantsToUseSkill = random.nextDouble() < skillUseProbability;
|
||||
|
||||
if (canUseSkill && wantsToUseSkill) {
|
||||
// 스킬 사용
|
||||
currentHp -= skillDamage;
|
||||
totalDamage += skillDamage;
|
||||
skillCount++;
|
||||
gcdRemaining = gcd; // GCD 시작
|
||||
} else {
|
||||
// 일반 공격
|
||||
currentHp -= normalAttackDamage;
|
||||
totalDamage += normalAttackDamage;
|
||||
}
|
||||
}
|
||||
|
||||
// 무한 루프 방지
|
||||
if (elapsedTime > 60000) break;
|
||||
}
|
||||
|
||||
totalCombatTime += elapsedTime;
|
||||
totalAttacks += attackCount;
|
||||
totalSkillUses += skillCount;
|
||||
}
|
||||
|
||||
final avgCombatTime = totalCombatTime / runs;
|
||||
final avgTotalAttacks = totalAttacks / runs;
|
||||
final avgSkillUses = totalSkillUses / runs;
|
||||
final skillRatio = avgSkillUses / avgTotalAttacks;
|
||||
final avgDps = totalDamage / runs / (avgCombatTime / 1000);
|
||||
|
||||
return SimulationResult(
|
||||
avgCombatTime: avgCombatTime,
|
||||
avgTotalAttacks: avgTotalAttacks,
|
||||
avgSkillUses: avgSkillUses,
|
||||
skillRatio: skillRatio,
|
||||
avgDps: avgDps,
|
||||
);
|
||||
}
|
||||
|
||||
class SimulationResult {
|
||||
const SimulationResult({
|
||||
required this.avgCombatTime,
|
||||
required this.avgTotalAttacks,
|
||||
required this.avgSkillUses,
|
||||
required this.skillRatio,
|
||||
required this.avgDps,
|
||||
});
|
||||
|
||||
final double avgCombatTime;
|
||||
final double avgTotalAttacks;
|
||||
final double avgSkillUses;
|
||||
final double skillRatio;
|
||||
final double avgDps;
|
||||
}
|
||||
Reference in New Issue
Block a user