using System.Text; namespace Colosseum.Combat.Simulation { /// /// 배치 시뮬레이션 결과를 추출용 문자열로 변환합니다. /// public static class SimulationBatchReportUtility { /// /// Markdown 리포트를 생성합니다. /// 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(); } /// /// CSV 리포트를 생성합니다. /// 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(); } /// /// 기본 파일 이름을 생성합니다. /// 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(' ', '_'); } } }