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 = {}; 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; }