/
expens1ve
/
project_war
Обзор
Документация
Войти
/
expens1ve
/
project_war
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
Persistence
UI/ConsoleUserInterface.cs
437 строк
15 KB
Диана Казиева
file encoding has been corrected
26 фев 2026, 11:11
26 фев 2026, 11:11
4f75605
Код
Авторство
О чём код?
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using project_war.Engine.Interfaces; using project_war.Models.Interfaces; namespace project_war.UI { /// <summary> /// Консольная реализация пользовательского интерфейса. /// </summary> public sealed class ConsoleUserInterface : IUserInterface { private const int DefaultLogCapacity = 200; private readonly IBattlefield _battlefield; private readonly Func<IReadOnlyList<IArmy>> _armiesProvider; private readonly Action? _createRandomArmies; private readonly Action? _initializeArmies; private readonly Action<string>? _saveCallback; private readonly Func<string, bool>? _loadCallback; private readonly ConcurrentQueue<string> _eventLog = new(); private bool _isRunning; /// <summary> /// Инициализирует новый экземпляр <see cref="ConsoleUserInterface"/>. /// </summary> public ConsoleUserInterface( IBattlefield battlefield, Func<IReadOnlyList<IArmy>> armiesProvider, Action? createRandomArmies = null, Action? initializeArmies = null, Action<string>? saveCallback = null, Func<string, bool>? loadCallback = null) { _battlefield = battlefield ?? throw new ArgumentNullException(nameof(battlefield)); _armiesProvider = armiesProvider ?? throw new ArgumentNullException(nameof(armiesProvider)); _createRandomArmies = createRandomArmies; _initializeArmies = initializeArmies; _saveCallback = saveCallback; _loadCallback = loadCallback; // Подписываемся на события движка; обработчики только помещают сообщения в очередь. _battlefield.OnUnitAttacked += OnUnitAttacked; _battlefield.OnUnitDied += OnUnitDied; } /// <inheritdoc /> public void Run() { _isRunning = true; PrintHeader(); while (_isRunning) { try { RenderState(); Console.Write("> "); var input = ConsoleHelpers.ReadLineSafe(); if (string.IsNullOrWhiteSpace(input)) { continue; } var parts = input!.Trim().Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); var cmd = parts[0].ToLowerInvariant(); var arg = parts.Length > 1 ? parts[1] : null; switch (cmd) { case "s": case "step": ExecuteStep(); break; case "r": case "run": ExecuteRunUntilEnd(); break; case "create-random": ExecuteCreateRandom(); break; case "init": case "initialize": ExecuteInitialize(); break; case "save": ExecuteSave(arg); break; case "load": ExecuteLoad(arg); break; case "log": PrintFullLog(); break; case "help": PrintHelp(); break; case "exit": case "quit": _isRunning = false; break; default: Console.WriteLine("Неизвестная команда. Введите 'help' для списка команд."); break; } } catch (Exception ex) { // Только основная нить отвечает за вывод - цветной вывод здесь безопасен. ConsoleHelpers.WriteLineColored($"Ошибка: {ex.Message}", ConsoleColor.Red); } } } #region Command implementations private void ExecuteStep() { _battlefield.ResolveCombat(); } private void ExecuteRunUntilEnd() { var armies = SafeGetArmies(); if (armies == null || armies.Count == 0) { Console.WriteLine("Нет доступных армий для запуска битвы."); return; } while (AreMultipleArmiesAlive()) { _battlefield.ResolveCombat(); } Console.WriteLine("Битва завершена."); } private void ExecuteCreateRandom() { if (_createRandomArmies == null) { Console.WriteLine("Callback для create-random не предоставлен."); return; } _createRandomArmies.Invoke(); Console.WriteLine("Случайные армии созданы."); } private void ExecuteInitialize() { if (_initializeArmies == null) { Console.WriteLine("Callback инициализации не предоставлен."); return; } _initializeArmies.Invoke(); Console.WriteLine("Инициализация выполнена."); } private void ExecuteSave(string? path) { if (string.IsNullOrWhiteSpace(path)) { Console.WriteLine("Использование: save <путь>"); return; } if (_saveCallback == null) { Console.WriteLine("Callback сохранения не предоставлен."); return; } try { _saveCallback.Invoke(path); Console.WriteLine($"Игра сохранена в: {path}"); } catch (Exception ex) { Console.WriteLine($"Не удалось сохранить: {ex.Message}"); } } private void ExecuteLoad(string? path) { if (string.IsNullOrWhiteSpace(path)) { Console.WriteLine("Использование: load <путь>"); return; } if (_loadCallback == null) { Console.WriteLine("Callback загрузки не предоставлен."); return; } try { var success = _loadCallback.Invoke(path); Console.WriteLine(success ? $"Игра загружена из: {path}" : $"Не удалось загрузить игру из: {path}"); } catch (Exception ex) { Console.WriteLine($"Не удалось загрузить: {ex.Message}"); } } #endregion #region Rendering / Helpers private void RenderState() { Console.Clear(); PrintHeaderSmall(); var armies = SafeGetArmies(); if (armies == null || armies.Count == 0) { Console.WriteLine("Армий для отображения нет. Используйте 'create-random' или вызовите callback инициализации."); Console.WriteLine(); PrintCommandsShort(); return; } foreach (var army in armies) { Console.WriteLine($"Армия: {army.Name} (Юнитов: {army.Units.Count}){(army.IsDefeated ? " [ПОРАЖЕНИЕ]" : string.Empty)}"); for (var i = 0; i < army.Units.Count; i++) { var u = army.Units[i]; var line = ConsoleHelpers.FormatUnitLine(i, u.GetType().Name, u.Health, u.AttackDamage, u.Defense, u.Cost); Console.WriteLine(line); } Console.WriteLine(); } // Вывод последних событий (печать в UI-потоке, с цветом) PrintRecentEvents(8); PrintCommandsShort(); } private void PrintHeader() { Console.Clear(); ConsoleHelpers.WriteCenteredHeader("========================================"); ConsoleHelpers.WriteCenteredHeader("Project War - консольный интерфейс"); ConsoleHelpers.WriteCenteredHeader("========================================"); Console.WriteLine(); PrintHelp(); } private void PrintHeaderSmall() { ConsoleHelpers.WriteCenteredHeader("Project War - консольный интерфейс (введите 'help')"); ConsoleHelpers.WriteSeparator(); } private void PrintCommandsShort() { Console.WriteLine(); Console.WriteLine("Команды: step | s, run | r, create-random, initialize | init, save <путь>, load <путь>, log, help, exit"); } private void PrintHelp() { Console.WriteLine("Команды:"); Console.WriteLine(" step | s - выполнить один шаг/акт боя"); Console.WriteLine(" run | r - выполнять шаги, пока не закончится битва"); Console.WriteLine(" create-random - (необязательно) создать случайные армии через callback"); Console.WriteLine(" initialize | init - (необязательно) зарегистрировать армии в движке через callback"); Console.WriteLine(" save <путь> - (необязательно) сохранить игру по пути через callback"); Console.WriteLine(" load <путь> - (необязательно) загрузить игру по пути через callback"); Console.WriteLine(" log - показать полный журнал событий"); Console.WriteLine(" help - показать это сообщение"); Console.WriteLine(" exit | quit - выйти из приложения"); Console.WriteLine(); } private void PrintRecentEvents(int count) { if (_eventLog.IsEmpty) { Console.WriteLine("Пока нет событий."); return; } Console.WriteLine("Последние события:"); var events = _eventLog.ToArray(); var toShow = events.Skip(Math.Max(0, events.Length - count)).ToArray(); foreach (var line in toShow) { // Определяем цвет по ключевому слову в строке лога if (line.Contains("АТАКА:", StringComparison.OrdinalIgnoreCase)) { ConsoleHelpers.WriteLineColored(" " + line, ConsoleColor.Yellow); } else if (line.Contains("ПОГИБ:", StringComparison.OrdinalIgnoreCase) || line.Contains("ПОГИБЛ", StringComparison.OrdinalIgnoreCase)) { ConsoleHelpers.WriteLineColored(" " + line, ConsoleColor.Red); } else { Console.WriteLine(" " + line); } } Console.WriteLine(); } private void PrintFullLog() { Console.Clear(); Console.WriteLine("Полный журнал событий:"); foreach (var line in _eventLog.ToArray()) { if (line.Contains("АТАКА:", StringComparison.OrdinalIgnoreCase)) { ConsoleHelpers.WriteLineColored(line, ConsoleColor.Yellow); } else if (line.Contains("ПОГИБ:", StringComparison.OrdinalIgnoreCase) || line.Contains("ПОГИБЛ", StringComparison.OrdinalIgnoreCase)) { ConsoleHelpers.WriteLineColored(line, ConsoleColor.Red); } else { Console.WriteLine(line); } } Console.WriteLine(); Console.WriteLine("Нажмите Enter, чтобы продолжить..."); ConsoleHelpers.ReadLineSafe(); } #endregion #region Event handlers private void OnUnitAttacked(IUnit attacker, IUnit target, int damage) { // Только собираем строку лога и кладем в очередь. var msg = $"{Timestamp()} АТАКА: {ShortUnit(attacker)} -> {ShortUnit(target)} урон:{damage} HP_цели:{target.Health}"; EnqueueEvent(msg); } private void OnUnitDied(IUnit dead) { var msg = $"{Timestamp()} ПОГИБ: {ShortUnit(dead)}"; EnqueueEvent(msg); } private static string Timestamp() => DateTime.Now.ToString("HH:mm:ss"); private static string ShortUnit(IUnit u) { if (u == null) { return "<null>"; } return $"{u.GetType().Name}(HP:{u.Health})"; } private void EnqueueEvent(string line) { _eventLog.Enqueue(line); while (_eventLog.Count > DefaultLogCapacity && _eventLog.TryDequeue(out _)) { // поддерживаем ограничение размера журнала } } #endregion #region Utilities private IReadOnlyList<IArmy>? SafeGetArmies() { try { return _armiesProvider.Invoke(); } catch { return null; } } private bool AreMultipleArmiesAlive() { var armies = SafeGetArmies(); if (armies == null || armies.Count == 0) { return false; } var aliveCount = armies.Count(a => !a.IsDefeated); return aliveCount > 1; } #endregion /// <inheritdoc /> public void Dispose() { try { _battlefield.OnUnitAttacked -= OnUnitAttacked; _battlefield.OnUnitDied -= OnUnitDied; } catch { // игнорируем ошибки при освобождении } } } }