/
evsyukov.s
/
DiceGame
Обзор
Документация
Войти
/
evsyukov.s
/
DiceGame
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
src/DicePoker.ConsoleApp/UI/GameController.cs
284 строки
11 KB
chernova.v
Добавлена обработка ошибок
20 дек 2025, 14:28
20 дек 2025, 14:28
9fe0ba8
Код
Авторство
О чём код?
using DicePoker.Core.Game; using DicePoker.Core.Logic; using DicePoker.Core.Models; using DicePoker.Core.Services; namespace DicePoker.ConsoleApp.UI { /// <summary> /// Класс для управления игровым циклом и оркестрации игрового процесса. /// Отвечает за запуск игры, обработку действий игроков и завершение игры. /// </summary> public class GameController { private readonly GamePersistenceService _gamePersistenceService; private readonly RecordsManagementService _recordsService; private readonly MenuManager _menuManager; /// <summary> /// Инициализирует новый экземпляр класса GameController. /// </summary> public GameController( GamePersistenceService gamePersistenceService, RecordsManagementService recordsService, MenuManager menuManager) { _gamePersistenceService = gamePersistenceService; _recordsService = recordsService; _menuManager = menuManager; } /// <summary> /// Запускает новую игру с двумя игроками. /// Запрашивает имена игроков и опциональное ограничение времени на ход, /// затем начинает основной игровой цикл. /// </summary> public async Task StartNewGameAsync() { Console.Clear(); ConsoleUi.PrintHeader(); // Запрашиваем имена двух игроков string p1 = InputHandler.ReadPlayerName("Имя игрока 1: ", "Игрок 1"); string p2 = InputHandler.ReadPlayerName("Имя игрока 2: ", "Игрок 2"); // Запрашиваем опциональное ограничение времени на ход int secondsPerMove = InputHandler.ReadIntegerWithValidation( "Ограничение времени на ход (сек, 0 = без ограничения): ", minValue: 0); // Создаем новое состояние игры и запускаем основной цикл GameState state = new GameState(p1, p2, secondsPerMove); await RunGameLoopAsync(state); } /// <summary> /// Загружает сохраненную игру и продолжает её. /// Если сохранения нет или оно повреждено, выводит соответствующее сообщение. /// </summary> public async Task LoadAndResumeAsync() { // Проверяем, существует ли сохранение bool hasSave = await _gamePersistenceService.HasSavedGameAsync(); if (!hasSave) { Console.WriteLine("\nНет сохранения. Нажмите Enter."); Console.ReadLine(); return; } // Загружаем сохраненное состояние игры GameState? state = await _gamePersistenceService.LoadGameAsync(); if (state == null) { Console.WriteLine("\nОшибка загрузки сохранения. Нажмите Enter."); Console.ReadLine(); return; } // Продолжаем игру с загруженным состоянием await RunGameLoopAsync(state, isLoadedGame: true); } /// <summary> /// Реализует основной игровой цикл. /// Обрабатывает ходы двух игроков, управляет состоянием игры, сохранениями и отменой последнего действия. /// Завершается после завершения полной игры (по 1 ходу каждого игрока). /// </summary> /// <param name="state">Состояние игры</param> /// <param name="isLoadedGame">Флаг, указывающий, загружена ли игра из сохранения (true) или это новая игра (false)</param> private async Task RunGameLoopAsync(GameState state, bool isLoadedGame = false) { // Стек для отката последнего сделанного переброса (функция undo) Stack<GameSnapshot> undoStack = new Stack<GameSnapshot>(); // Флаг для отслеживания первой итерации цикла bool isFirstIteration = true; while (true) { Console.Clear(); ConsoleUi.PrintHeader(); ConsoleUi.PrintScores(state); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"\nХод: {state.CurrentPlayer.Name} (пере-бросов осталось: {state.RerollsLeft})"); Console.ResetColor(); // Начинаем новый ход текущего игрока // Для загруженной игры пропускаем StartTurn() на первой итерации, т.к. кости уже были брошены if (!isLoadedGame || !isFirstIteration) { state.StartTurn(); } isFirstIteration = false; // Цикл действий в рамках одного хода while (true) { Console.WriteLine(); string mask = HintGenerator.SuggestKeepMask(state.Hand.Values()); ConsoleUi.DrawDice(state.Hand.Values(), mask); _menuManager.ShowGameActionsMenu(); // Читаем ввод с учетом опционального ограничения времени string? answer; if (state.SecondsPerMove > 0) { // Если установлено ограничение времени, используем функцию с таймаутом answer = InputHandler.ReadLineWithTimeout(state.SecondsPerMove); if (answer is null) { Console.WriteLine("\nВремя истекло. Ход завершён автоматически."); FinalizeTurn(state); break; } } else { // Без ограничения времени используем стандартный ввод answer = InputHandler.ReadChoice("\nВаш выбор: "); } // Обрабатываем выбор игрока switch (answer) { case "1": // Переброс выбранных костей if (!HandleRerollAction(state, undoStack)) { continue; } break; case "2": // Завершить ход и подсчитать очки FinalizeTurn(state); break; case "3": // Сохранить текущее состояние игры в файл await _gamePersistenceService.SaveGameAsync(state); Console.WriteLine("Игра сохранена."); break; case "4": // Показать историю последних ходов (max 20) ConsoleUi.ShowHistory(state); break; case "5": // Отменить последний переброс (если возможно) HandleUndoAction(ref state, undoStack); break; case "6": // Выход в главное меню return; default: Console.WriteLine("Неизвестная команда."); break; } // Если выбран пункт 2, выходим из цикла действий хода if (answer == "2") { break; } } // Проверяем, завершена ли игра (после 2-х ходов) if (state.TurnNumber > 2) { await ShowGameResultsAsync(state); return; } } } /// <summary> /// Обрабатывает действие переброса костей. /// </summary> private bool HandleRerollAction(GameState state, Stack<GameSnapshot> undoStack) { if (!state.CanReroll) { Console.WriteLine("Лимит перебросов в ходу — 2. Больше перебрасывать нельзя."); return false; } Console.Write("Введите индексы костей для переброса (0-4, через пробел): "); string input = Console.ReadLine() ?? ""; // Валидация: проверяем, что все введенные значения - корректные числа от 0 до 4 if (!InputHandler.AreIndicesValid(input)) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Ошибка: введены некорректные данные. Используйте только числа от 0 до 4."); Console.ResetColor(); Console.WriteLine("Нажмите Enter для продолжения..."); Console.ReadLine(); return false; } int[] indices = InputHandler.ParseIndices(input); // Сохраняем текущее состояние в стек для отката undoStack.Push(GameSnapshot.FromState(state)); state.Reroll(indices); return true; } /// <summary> /// Обрабатывает действие отмены последнего переброса. /// </summary> private void HandleUndoAction(ref GameState state, Stack<GameSnapshot> undoStack) { if (undoStack.Count > 0) { GameSnapshot snap = undoStack.Pop(); state = snap.ToState(); Console.WriteLine("Откат выполнен."); } else { Console.WriteLine("Нечего отменять."); } } /// <summary> /// Завершает ход текущего игрока, показывает результат комбинации и очков. /// Затем передает ход следующему игроку. /// </summary> private void FinalizeTurn(GameState state) { // Завершаем ход, подсчитываем комбинацию и добавляем очки к счету игрока HandRank rank = state.EndTurnAndScore(); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($"\nКомбинация: {ConsoleUi.GetCategoryName(rank.Category)}, очки: {rank.ScoreValue}"); Console.ResetColor(); Console.WriteLine("Enter для продолжения..."); Console.ReadLine(); } /// <summary> /// Отображает результаты завершенной игры и сохраняет рекорды. /// </summary> private async Task ShowGameResultsAsync(GameState state) { Console.Clear(); ConsoleUi.PrintHeader(); ConsoleUi.PrintScores(state); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("\nИгра завершена!"); // Определяем победителя string winner = state.Player1.Score == state.Player2.Score ? "Ничья" : state.Player1.Score > state.Player2.Score ? state.Player1.Name : state.Player2.Name; Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine($"Победитель: {winner}"); Console.ResetColor(); // Сохраняем рекорды обоих игроков в таблицу лучших await _recordsService.AddRecordAsync(state.Player1.Name, state.Player1.Score); await _recordsService.AddRecordAsync(state.Player2.Name, state.Player2.Score); Console.WriteLine("\nEnter чтобы вернуться в меню."); Console.ReadLine(); } } }