/
puma
/
ppa
Обзор
Документация
Войти
/
puma
/
ppa
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
Battle/BattleDisplayer.cs
668 строк
29 KB
Margarita
new
29 май 2026, 17:05
29 май 2026, 17:05
cc969a9
Код
Авторство
О чём код?
using System; using System.Collections.Generic; using System.Linq; using System.Text; /// <summary> /// Отвечает за отображение информации о боевой системе /// Отделена от логики боя /// </summary> public class BattleDisplayer : IBattleDisplay { private static string GetUnitLabel(int localIndex, bool isPlayer1) => isPlayer1 ? $"1_{localIndex + 1}" : $"2_{localIndex + 1}"; private static string GetAbilityShortInfo(IUnit unit) => unit.SpecialAbility switch { ArrowAbility a => $"Стр:{a.Power}", HealAbility h => $"Леч:{h.HealPower}", CloneAbility c => $"Клон:{c.CloneProbability}%", SquireAbility s => $"Буфф:{s.ActivationProbability}%", null => "-", _ => "?" }; public void ShowBattleStart(string firstPlayerName) { try { Console.Clear(); } catch { } Console.WriteLine(new string('=', 63)); Console.WriteLine("=== НАЧАЛО БИТВЫ ==="); Console.WriteLine(new string('=', 63) + "\n"); Console.WriteLine($"Первым ходит: {firstPlayerName}\n"); Console.ReadKey(); } public void ShowRound(int roundNumber, int player1UnitsCount, int player2UnitsCount, string player1Name, string player2Name) { try { Console.Clear(); } catch { } Console.WriteLine(new string('=', 63)); Console.WriteLine($"=== РАУНД {roundNumber} ==="); Console.WriteLine($"{player1Name}: {player1UnitsCount} юнитов"); Console.WriteLine($"{player2Name}: {player2UnitsCount} юнитов"); Console.WriteLine(new string('=', 63)); } public void DisplayArmiesStatus(string currentPlayerName, string armyVisualization1, string allUnitsHealth1, string allUnitsAbilities1, string opponentName, string armyVisualization2, string allUnitsHealth2, string allUnitsAbilities2, List<string>? allUnitsBuffs1 = null, List<string>? allUnitsBuffs2 = null) { Console.WriteLine("\n" + new string('=', 63)); Console.WriteLine($"{currentPlayerName}: {armyVisualization1}"); Console.WriteLine($"{currentPlayerName}:{allUnitsHealth1}"); Console.WriteLine($"{currentPlayerName}:{allUnitsAbilities1}"); // Вывести баффы если они есть if (allUnitsBuffs1 != null && allUnitsBuffs1.Count > 0) { foreach (var buffLine in allUnitsBuffs1) { Console.WriteLine($"{currentPlayerName}:{buffLine}"); } } Console.WriteLine(new string('=', 63)); Console.WriteLine($"{opponentName}: {armyVisualization2}"); Console.WriteLine($"{opponentName}:{allUnitsHealth2}"); Console.WriteLine($"{opponentName}:{allUnitsAbilities2}"); // Вывести баффы если они есть if (allUnitsBuffs2 != null && allUnitsBuffs2.Count > 0) { foreach (var buffLine in allUnitsBuffs2) { Console.WriteLine($"{opponentName}:{buffLine}"); } } Console.WriteLine(new string('=', 63)); } public void DisplayGameStatistics(string winnerName, int roundNumber, List<(string Name, int CurrentHealth, int MaxHealth)> aliveUnits) { Console.WriteLine("\n" + new string('=', 63)); Console.WriteLine($"=== СТАТИСТИКА БОЯ ==="); Console.WriteLine(new string('=', 63)); Console.WriteLine($"Всего раундов: {roundNumber,-27} ="); Console.WriteLine("\n"); Console.WriteLine($"ПОБЕДИТЕЛЬ: {winnerName,-27} ="); Console.WriteLine(new string('=', 66)); Console.WriteLine($" Оставшаяся армия: "); if (aliveUnits.Count > 0) { foreach (var (name, currentHealth, maxHealth) in aliveUnits) { string unitInfo = $" {name} HP:{currentHealth}/{maxHealth}"; Console.WriteLine($"║ {unitInfo,-38} ║"); } } else { Console.WriteLine("Нет живых юнитов "); } Console.WriteLine(new string('=', 63)); } /// <summary> /// Вывести статистику при ничье (10+ раундов без изменений) /// </summary> public void DisplayDrawStatistics(int roundNumber) { Console.WriteLine("\n" + new string('=', 63)); Console.WriteLine($"=== СТАТИСТИКА БОЯ ==="); Console.WriteLine(new string('=', 63)); Console.WriteLine($"Всего раундов: {roundNumber,-27} ="); Console.WriteLine("\n"); Console.WriteLine($"РЕЗУЛЬТАТ: НИЧЬЯ ="); Console.WriteLine($"Причина: 10+ раундов без изменений ="); Console.WriteLine(new string('=', 63)); } public void ShowArcherAttackResult(string archerName, string archerLabel, string targetName, string targetLabel, int damageDealt, int currentHealth, int maxHealth) { Console.WriteLine($"\n{archerLabel} {archerName} стреляет в {targetLabel} {targetName}!"); Console.WriteLine($"Урон: {damageDealt}"); Console.WriteLine($"Здоровье цели: {currentHealth}/{maxHealth}"); } public void ShowArcherNoTargets(string archerName, string archerLabel, int range) { Console.WriteLine($"\n{archerLabel} {archerName} имеет маленький радиус ({range}), нет целей для стрельбы."); } public void ShowHealerAttackResult(string targetName, string targetLabel, int healAmount, int currentHealth, int maxHealth) { Console.WriteLine($"\n{targetLabel} {targetName} вылечен на {healAmount} HP."); Console.WriteLine($"Здоровье: {currentHealth}/{maxHealth}"); } public void ShowWizardCloneAttempt(string wizardName, string wizardLabel, string targetName, int cloneProbability) { Console.WriteLine($"\n{wizardLabel} {wizardName} пытается клонировать {targetName} (вероятность {cloneProbability}%)..."); } public void ShowWizardCloneSuccess(string originalUnitName, string originalLabel, string cloneName) { Console.WriteLine($"\n✓ Клонирование успешно!"); Console.WriteLine($"Волшебник создал копию юнита {originalLabel} ({originalUnitName})"); Console.WriteLine($"Клон ({cloneName}) добавлен перед волшебником."); } public void ShowWizardCloneFailed(string targetUnitName, string targetLabel, int cloneProbability) { Console.WriteLine($"\n✗ Клонирование не удалось!"); Console.WriteLine($"Волшебник попытался клонировать юнита {targetLabel} ({targetUnitName}), но магия не сработала..."); } public void ShowWizardNoTargets(string wizardName, string wizardLabel) { Console.WriteLine($"\n{wizardLabel} {wizardName} не нашел никого для клонирования."); } public void ShowHealerNoTargets(string healerName, string healerLabel) { Console.WriteLine($"\n{healerLabel} {healerName} не нашел никого, кого нужно лечить."); } public void ShowUnitDefeated(string unitName, string reason) { Console.WriteLine($"{unitName} {reason}!"); } public void ShowNewUnitAppears(string unitName) { Console.WriteLine($"На поле выходит {unitName}!"); } /// <summary> /// Получить визуализацию армии с ID номерами в скобках (в обратном порядке) /// A - Archer, H - Heavy, L - Light, E - Healer, W - Wizard /// Формат: L(37) A(26) V(2) и т.д. /// </summary> public string GetArmyVisualization(IArmy army) { var symbols = new List<string>(); // Показываем юнитов в обратном порядке (для удобного чтения слева направо) for (int i = army.Units.Count - 1; i >= 0; i--) { var unit = army.Units[i]; if (unit.IsAlive) { string unitSymbol = $"{unit.GetDisplaySymbol()}({unit.Id})".PadRight(8); symbols.Add(unitSymbol); } } return string.Concat(symbols); } /// <summary> /// Получить информацию о здоровье текущего юнита /// </summary> public string GetCurrentUnitHealth(IArmy army) { var currentUnit = army.GetCurrentUnit(); if (currentUnit != null && currentUnit.IsAlive) { return $"{currentUnit.Name}: {currentUnit.CurrentHealth}/{currentUnit.MaxHealth}"; } return "Нет живых юнитов"; } /// <summary> /// Получить информацию о здоровье всех юнитов армии в виде строки /// Каждое значение HP выровнено в колонку шириной 8 символов /// </summary> public string GetAllUnitsHealth(IArmy army) { var healthStrings = new List<string>(); for (int i = army.Units.Count - 1; i >= 0; i--) { var unit = army.Units[i]; if (unit.IsAlive) { healthStrings.Add($"{unit.CurrentHealth}/{unit.MaxHealth}".PadRight(8)); } } return healthStrings.Count > 0 ? string.Concat(healthStrings) : "Нет живых юнитов"; } /// <summary> /// Получить информацию о способностях всех юнитов армии в виде строки /// </summary> public string GetAllUnitsAbilities(IArmy army) { var abilityStrings = new List<string>(); for (int i = army.Units.Count - 1; i >= 0; i--) { var unit = army.Units[i]; if (unit.IsAlive) abilityStrings.Add(GetAbilityShortInfo(unit).PadRight(8)); } return abilityStrings.Count > 0 ? string.Concat(abilityStrings) : "Нет живых юнитов"; } /// <summary> /// Получить информацию о баффах всех юнитов армии в виде многострочной строки /// Каждый юнит может иметь несколько баффов, выводятся в столбик (каждый с новой строки) /// </summary> public List<string> GetAllUnitsBuffs(IArmy army) { var buffLines = new List<string>(); // Собрать баффы для каждого юнита var unitBuffs = new List<List<string>>(); for (int i = army.Units.Count - 1; i >= 0; i--) { var unit = army.Units[i]; var buffs = new List<string>(); if (unit.IsAlive && unit is IBuffable buffableUnit) buffs.AddRange(buffableUnit.GetActiveBuffs().Select(BuffFormatter.GetShortEn)); unitBuffs.Add(buffs); } // Найти максимальное количество баффов у одного юнита int maxBuffCount = unitBuffs.Max(b => b.Count); // Если нет баффов, вернуть пустой список if (maxBuffCount == 0) { return buffLines; } // Построить многострочное представление for (int buffIndex = 0; buffIndex < maxBuffCount; buffIndex++) { var buffLine = new List<string>(); for (int unitIndex = 0; unitIndex < unitBuffs.Count; unitIndex++) { var unitBuffList = unitBuffs[unitIndex]; if (buffIndex < unitBuffList.Count) { buffLine.Add(unitBuffList[buffIndex].PadRight(12)); } else { buffLine.Add("-".PadRight(12)); } } buffLines.Add(string.Concat(buffLine)); } return buffLines; } /// <summary> /// Показать заголовок обновленного статуса после хода /// </summary> public void ShowStatusUpdate() { Console.WriteLine("\n" + new string('=', 63)); Console.WriteLine("=== СОСТОЯНИЕ ПОСЛЕ ХОДА ==="); Console.WriteLine(new string('=', 63)); } /// <summary> /// Оруженосец не нашел цели /// </summary> public void ShowSquireNoTargets(string squireName, string squireLabel) { Console.WriteLine($"\n{squireLabel} {squireName} (оруженосец) нет соседних тяжелых воинов для буффа."); } /// <summary> /// Попытка оруженосца применить бафф /// </summary> public void ShowSquireBuffAttempt(string squireName, string squireLabel, string targetName, string targetLabel, int probability) { Console.WriteLine($"\n{squireLabel} {squireName} (оруженосец) пытается наложить бафф на {targetLabel} {targetName}!"); Console.WriteLine($"Вероятность: {probability}%"); } /// <summary> /// Бафф успешно наложен /// </summary> public void ShowSquireBuffSuccess(string squireName, string squireLabel, string targetName, string targetLabel, string buffName) { Console.WriteLine($"✓ Успех! {targetLabel} {targetName} получил бафф: {buffName}!"); } /// <summary> /// Бафф не сработал /// </summary> public void ShowSquireBuffFailed(string squireName, string squireLabel, string targetName, string targetLabel, int probability) { Console.WriteLine($"✗ {squireLabel} {squireName} не смог наложить бафф на {targetLabel} {targetName} (вероятность была {probability}%)"); } /// <summary> /// Бафф слетел /// </summary> public void ShowBuffLost(string unitName) { Console.WriteLine($"⚠ {unitName} потеряет один бафф от удара противника!"); } /// <summary>Выравнивает текст по центру в поле заданной ширины, обрезая если длиннее.</summary> private string CenterText(string text, int width) { if (text.Length > width) text = text.Substring(0, width); // обрезка int totalPadding = width - text.Length; int leftPadding = totalPadding / 2; return new string(' ', leftPadding) + text + new string(' ', totalPadding - leftPadding); } /// <summary> /// Строит текстовое представление армии для формации "Мост": символ, HP и способность /// каждого юнита в колонках по <c>COLUMN_WIDTH</c> символов, группами по 9. /// </summary> public string DisplayBridgeFormation(IArmy army) { var aliveUnits = army.Units.Where(u => u.IsAlive).ToList(); const int COLUMN_WIDTH = 12; const int MAX_UNITS_PER_ROW = 9; var result = new System.Text.StringBuilder(); // Разбиваем юнитов на группы по 9 for (int groupStart = 0; groupStart < aliveUnits.Count; groupStart += MAX_UNITS_PER_ROW) { int groupEnd = Math.Min(groupStart + MAX_UNITS_PER_ROW, aliveUnits.Count); string line1 = ""; // символы с номерами string line2 = ""; // здоровье string line3 = ""; // способности for (int i = groupStart; i < groupEnd; i++) { var unit = aliveUnits[i]; string symbol = $"{unit.GetDisplaySymbol()}({unit.Id})"; string health = $"{unit.CurrentHealth}/{unit.MaxHealth}"; string ability = GetUnitInfoWithAbilitiesAndBuffs(unit) .Replace("(", "") .Replace(")", ""); // если длинная строка → усечь или заменить if (ability.Length > COLUMN_WIDTH) { ability = ability.Substring(0, Math.Min(COLUMN_WIDTH - 1, ability.Length)); } else if (string.IsNullOrWhiteSpace(ability)) { ability = "-"; } line1 += CenterText(symbol, COLUMN_WIDTH); line2 += CenterText(health, COLUMN_WIDTH); line3 += CenterText(ability, COLUMN_WIDTH); } if (result.Length > 0) { result.Append("\n\n\n\n"); // 3 пустые строки между группами } result.Append(line1); result.Append("\n"); result.Append(line2); result.Append("\n"); result.Append(line3); } return result.ToString(); } /// <summary> /// Вывести армию в режиме 3 на 3 /// </summary> public string DisplayThreeOnThreeFormation(IArmy army) { // Поддержка для старого вызова без глобальных номеров return DisplayThreeOnThreeFormation(army, army.Units.Count, true); } public string DisplayThreeOnThreeFormation(IArmy army, int player1ArmySize, bool isPlayer1) { const int UNITS_PER_ROW = 3; var aliveUnits = army.Units.Where(u => u.IsAlive).ToList(); string result = "\n╔═══════════════════════════════════════════════════════════════╗\n║ РЯД 1 (НА РИНГЕ):\n"; // Ряд 1 (на ринге) for (int i = 0; i < UNITS_PER_ROW && i < aliveUnits.Count; i++) { var unit = aliveUnits[i]; string health = $"{unit.CurrentHealth}/{unit.MaxHealth}"; string abilityBuffInfo = GetUnitInfoWithAbilitiesAndBuffs(unit); string label = GetUnitLabel(i, isPlayer1); result += $"║ [{label}] {unit.Name} {health}{abilityBuffInfo}\n"; // Добавить баффы на отдельной строке если они есть string buffLine = GetBuffInfoLine(unit); if (!string.IsNullOrEmpty(buffLine)) { result += $"║ {buffLine}\n"; } } result += "╠═══════════════════════════════════════════════════════════════╣\n"; // Режим (в запасе) int rowNum = 2; for (int i = UNITS_PER_ROW; i < aliveUnits.Count; i++) { if (i % UNITS_PER_ROW == 0) { if (i > UNITS_PER_ROW) result += "║\n"; result += $"║ РЯД {rowNum} (ЗАПАС):\n"; rowNum++; } var unit = aliveUnits[i]; string health = $"{unit.CurrentHealth}/{unit.MaxHealth}"; string abilityBuffInfo = GetUnitInfoWithAbilitiesAndBuffs(unit); string label = GetUnitLabel(i, isPlayer1); result += $"║ [{label}] {unit.Name} {health}{abilityBuffInfo}\n"; // Добавить баффы на отдельной строке если они есть string buffLine = GetBuffInfoLine(unit); if (!string.IsNullOrEmpty(buffLine)) { result += $"║ {buffLine}\n"; } } result += "╚═══════════════════════════════════════════════════════════════╝"; return result; } /// <summary> /// Получить информацию о юните с абилити и баффами в скобках /// </summary> private static string GetUnitInfoWithAbilitiesAndBuffs(IUnit unit) { if (unit.SpecialAbility == null) return ""; string info = GetAbilityShortInfo(unit); return info == "-" ? "" : $" ({info})"; } /// <summary> /// Получить информацию о баффах юнита для отдельной строки (если есть) /// </summary> private static string GetBuffInfoLine(IUnit unit) { if (unit is not IBuffable buffable) return ""; var buffs = buffable.GetActiveBuffs(); return buffs.Count > 0 ? string.Join(" ", buffs.Select(BuffFormatter.GetShortRu)) : ""; } /// <summary> /// Получить полную информацию о юните для 3 на 3: здоровье, абилити и баффы в одной строке /// </summary> private static string GetUnitInfoFullInline(IUnit unit) { var extras = new List<string>(); string ability = GetAbilityShortInfo(unit); if (ability != "-") extras.Add(ability); if (unit is IBuffable buffable) extras.AddRange(buffable.GetActiveBuffs().Select(BuffFormatter.GetShortRu)); return extras.Count > 0 ? $" ({string.Join(" ", extras)})" : ""; } /// <summary> /// Вывести армию в режиме стенка на стенку /// </summary> public string DisplayWallToWallFormation(IArmy army) { var aliveUnits = army.Units.Where(u => u.IsAlive).ToList(); string result = "\n═══════════════════════════════════════════════════════════════\n"; result += " ФРОНТАЛЬНАЯ СТЕНКА:\n"; result += "═══════════════════════════════════════════════════════════════\n"; for (int i = 0; i < aliveUnits.Count; i++) { var unit = aliveUnits[i]; string abilityBuffInfo = GetUnitInfoWithAbilitiesAndBuffs(unit); string healthStr = $"{unit.CurrentHealth}/{unit.MaxHealth}"; result += $"║ позиция #{i + 1}: {unit.Name} {healthStr}{abilityBuffInfo}\n"; // Добавить баффы на отдельной строке если они есть string buffLine = GetBuffInfoLine(unit); if (!string.IsNullOrEmpty(buffLine)) { result += $"║ {buffLine}\n"; } } result += "═══════════════════════════════════════════════════════════════"; return result; } /// <summary> /// Вывести боевые позиции для режима 3 на 3 (противниковые пары) /// </summary> public string DisplayThreeOnThreeMatchups(IBattleFormation formation, IArmy army1, IArmy army2, string player1Name, string player2Name) { string result = "\n🥊 БОЕВЫЕ ПАРЫ (3 на 3):\n"; result += "════════════════════════════════════════════════════════════════════════════════════════════════════════\n"; var ringUnits1 = formation.GetRingUnits(army1); var ringUnits2 = formation.GetRingUnits(army2); int maxLen = Math.Max(ringUnits1.Count, ringUnits2.Count); for (int i = 0; i < maxLen; i++) { string unit1Full = "---"; string unit2Full = "---"; string buffLine1 = ""; string buffLine2 = ""; if (i < ringUnits1.Count) { var u1 = ringUnits1[i]; string health1 = $"{u1.CurrentHealth}/{u1.MaxHealth}"; string info1 = GetUnitInfoWithAbilitiesAndBuffs(u1); unit1Full = $"{u1.Name} {health1} {info1}"; buffLine1 = GetBuffInfoLine(u1); } if (i < ringUnits2.Count) { var u2 = ringUnits2[i]; string health2 = $"{u2.CurrentHealth}/{u2.MaxHealth}"; string info2 = GetUnitInfoWithAbilitiesAndBuffs(u2); unit2Full = $"{u2.Name} {health2} {info2}"; buffLine2 = GetBuffInfoLine(u2); } string marker1 = i < ringUnits1.Count ? "●" : "○"; string marker2 = i < ringUnits2.Count ? "●" : "○"; string label1 = GetUnitLabel(i, true); string label2 = GetUnitLabel(i, false); result += $"{label1}: {marker1} {unit1Full,-43} ──── {marker2} {label2}: {unit2Full,-43}\n"; // Добавить баффы на отдельной строке если они есть if (!string.IsNullOrEmpty(buffLine1) || !string.IsNullOrEmpty(buffLine2)) { result += $" {buffLine1,-43} ──── {buffLine2,-43}\n"; } } result += "════════════════════════════════════════════════════════════════════════════════════════════════════════\n"; return result; } /// <summary> /// Вывести боевые позиции для стенки на стенку /// </summary> public string DisplayWallToWallMatchups(IBattleFormation formation, IArmy army1, IArmy army2, string player1Name, string player2Name) { string result = "\n⚔️ БОЕВЫЕ ЛИНИИ:\n"; result += "════════════════════════════════════════════════════════════════════════════════════════════════════════\n"; var allUnits1 = army1.Units.Where(u => u.IsAlive).ToList(); var allUnits2 = army2.Units.Where(u => u.IsAlive).ToList(); int maxLen = Math.Max(allUnits1.Count, allUnits2.Count); for (int i = 0; i < maxLen; i++) { string unit1Full = "---"; string unit2Full = "---"; string buffLine1 = ""; string buffLine2 = ""; if (i < allUnits1.Count) { var u1 = allUnits1[i]; string health1 = $"{u1.CurrentHealth}/{u1.MaxHealth}"; string info1 = GetUnitInfoWithAbilitiesAndBuffs(u1); unit1Full = $"{u1.Name} {health1} {info1}"; buffLine1 = GetBuffInfoLine(u1); } if (i < allUnits2.Count) { var u2 = allUnits2[i]; string health2 = $"{u2.CurrentHealth}/{u2.MaxHealth}"; string info2 = GetUnitInfoWithAbilitiesAndBuffs(u2); unit2Full = $"{u2.Name} {health2} {info2}"; buffLine2 = GetBuffInfoLine(u2); } string marker1 = i < allUnits1.Count ? "●" : "○"; string marker2 = i < allUnits2.Count ? "●" : "○"; string label1 = GetUnitLabel(i, true); string label2 = GetUnitLabel(i, false); result += $"{label1}: {marker1} {unit1Full,-43} ──── {marker2} {label2}: {unit2Full,-43}\n"; // Добавить баффы на отдельной строке если они есть if (!string.IsNullOrEmpty(buffLine1) || !string.IsNullOrEmpty(buffLine2)) { result += $" {buffLine1,-43} ──── {buffLine2,-43}\n"; } } result += "════════════════════════════════════════════════════════════════════════════════════════════════════════\n"; return result; } }