feat: 허수아비 계산 시뮬레이터 추가
- 빌드 입력, 룰셋, 회전 정책, 결과/리포트 모델을 포함한 데미지 계산 시뮬레이터 기반을 추가 - 단일 실행 창과 배치 전수 조사 창, 플레이어 데미지 스윕 메뉴를 추가 - DamageEffect 계산값 접근자를 열어 기존 전투 공식을 시뮬레이터에서 재사용하도록 정리
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Colosseum.Combat.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// 배치 시뮬레이션 결과를 추출용 문자열로 변환합니다.
|
||||
/// </summary>
|
||||
public static class SimulationBatchReportUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Markdown 리포트를 생성합니다.
|
||||
/// </summary>
|
||||
public static string BuildMarkdown(SimulationBatchResult result)
|
||||
{
|
||||
if (result == null)
|
||||
return string.Empty;
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("# 허수아비 배치 시뮬레이션 결과");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine();
|
||||
builder.Append("- Batch: ");
|
||||
builder.Append(result.BatchName);
|
||||
builder.AppendLine();
|
||||
builder.Append("- Generated Builds: ");
|
||||
builder.Append(result.GeneratedBuildCount);
|
||||
builder.AppendLine();
|
||||
builder.Append("- Truncated: ");
|
||||
builder.Append(result.Truncated ? "Yes" : "No");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("| 순위 | 빌드 | DPS | 총 피해 | 총 마나 | 첫 사이클 |");
|
||||
builder.AppendLine("| --- | --- | ---: | ---: | ---: | ---: |");
|
||||
|
||||
for (int i = 0; i < result.Entries.Count; i++)
|
||||
{
|
||||
SimulationBatchEntry entry = result.Entries[i];
|
||||
SimulationResult simulation = entry != null ? entry.Result : null;
|
||||
if (simulation == null)
|
||||
continue;
|
||||
|
||||
builder.Append("| ");
|
||||
builder.Append(i + 1);
|
||||
builder.Append(" | ");
|
||||
builder.Append(entry.BuildLabel);
|
||||
builder.Append(" | ");
|
||||
builder.Append(simulation.AverageDps.ToString("0.##"));
|
||||
builder.Append(" | ");
|
||||
builder.Append(simulation.TotalDamage.ToString("0.##"));
|
||||
builder.Append(" | ");
|
||||
builder.Append(simulation.TotalManaUsed.ToString("0.##"));
|
||||
builder.Append(" | ");
|
||||
builder.Append(simulation.FirstCycleEndTime >= 0f ? simulation.FirstCycleEndTime.ToString("0.##") + "s" : "미완료");
|
||||
builder.AppendLine(" |");
|
||||
}
|
||||
|
||||
if (result.Warnings.Count > 0)
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("## 경고");
|
||||
builder.AppendLine();
|
||||
for (int i = 0; i < result.Warnings.Count; i++)
|
||||
{
|
||||
builder.Append("- ");
|
||||
builder.Append(result.Warnings[i]);
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CSV 리포트를 생성합니다.
|
||||
/// </summary>
|
||||
public static string BuildCsv(SimulationBatchResult result)
|
||||
{
|
||||
if (result == null)
|
||||
return string.Empty;
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.AppendLine("Rank,BuildLabel,RuleName,RotationName,DurationSeconds,TotalDamage,AverageDps,TotalManaUsed,AverageManaPerSecond,FirstCycleEndTime,TopSkill,TopSkillDamage,Warnings");
|
||||
|
||||
for (int i = 0; i < result.Entries.Count; i++)
|
||||
{
|
||||
SimulationBatchEntry entry = result.Entries[i];
|
||||
SimulationResult simulation = entry != null ? entry.Result : null;
|
||||
if (simulation == null)
|
||||
continue;
|
||||
|
||||
SimulationSkillBreakdown topSkill = simulation.SkillBreakdowns.Count > 0 ? simulation.SkillBreakdowns[0] : null;
|
||||
string warnings = string.Join(" / ", simulation.Warnings);
|
||||
|
||||
builder.Append(i + 1);
|
||||
builder.Append(',');
|
||||
builder.Append(Escape(entry.BuildLabel));
|
||||
builder.Append(',');
|
||||
builder.Append(Escape(simulation.RuleName));
|
||||
builder.Append(',');
|
||||
builder.Append(Escape(simulation.RotationName));
|
||||
builder.Append(',');
|
||||
builder.Append(simulation.DurationSeconds.ToString("0.##"));
|
||||
builder.Append(',');
|
||||
builder.Append(simulation.TotalDamage.ToString("0.##"));
|
||||
builder.Append(',');
|
||||
builder.Append(simulation.AverageDps.ToString("0.##"));
|
||||
builder.Append(',');
|
||||
builder.Append(simulation.TotalManaUsed.ToString("0.##"));
|
||||
builder.Append(',');
|
||||
builder.Append(simulation.AverageManaPerSecond.ToString("0.##"));
|
||||
builder.Append(',');
|
||||
builder.Append(simulation.FirstCycleEndTime >= 0f ? simulation.FirstCycleEndTime.ToString("0.##") : string.Empty);
|
||||
builder.Append(',');
|
||||
builder.Append(Escape(topSkill != null ? topSkill.SkillName : string.Empty));
|
||||
builder.Append(',');
|
||||
builder.Append(topSkill != null ? topSkill.TotalDamage.ToString("0.##") : string.Empty);
|
||||
builder.Append(',');
|
||||
builder.Append(Escape(warnings));
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
return builder.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 기본 파일 이름을 생성합니다.
|
||||
/// </summary>
|
||||
public static string BuildDefaultFileName(SimulationBatchResult result, bool csv)
|
||||
{
|
||||
string batchName = result != null && !string.IsNullOrWhiteSpace(result.BatchName) ? result.BatchName : "BuildSimulationBatch";
|
||||
string extension = csv ? "csv" : "md";
|
||||
return $"{Sanitize(batchName)}.{extension}";
|
||||
}
|
||||
|
||||
private static string Escape(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return string.Empty;
|
||||
|
||||
bool needsQuotes = value.Contains(",") || value.Contains("\"") || value.Contains("\n") || value.Contains("\r");
|
||||
if (!needsQuotes)
|
||||
return value;
|
||||
|
||||
return $"\"{value.Replace("\"", "\"\"")}\"";
|
||||
}
|
||||
|
||||
private static string Sanitize(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return "BuildSimulationBatch";
|
||||
|
||||
string sanitized = value;
|
||||
char[] invalidChars = System.IO.Path.GetInvalidFileNameChars();
|
||||
for (int i = 0; i < invalidChars.Length; i++)
|
||||
{
|
||||
sanitized = sanitized.Replace(invalidChars[i], '_');
|
||||
}
|
||||
|
||||
return sanitized.Replace(' ', '_');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user