/
MrFish
/
CSharpGameProject
Обзор
Документация
Войти
/
MrFish
/
CSharpGameProject
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
MainGameProject/Program.cs
1 085 строк
47 KB
Yaroslav4444-bauer
Кодстайл - исправлены сообщения
20 дек 2025, 14:46
20 дек 2025, 14:46
5149f1a
Код
Авторство
О чём код?
using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; namespace MainGameProject { // Enums // Типы биомов public enum TerrainType { Plain, // Neutral Forest, // Enhances Earth runes Desert, // Enhances Fire runes Ocean, // Enhances Water runes Mountain // Enhances Air runes } // Типы рун public enum RuneType { Fire, Water, Earth, Air } // Игровые действия public enum ActionType { CreateSpell, MoveTotem } // Game class - ядро игры public class Game { public Board Board { get; set; } public Player Player1 { get; set; } public Player Player2 { get; set; } public bool IsPlayer1Turn { get; set; } = true; public int GamePlayerIteration { get; set; } public int TurnTime { get; set; } // Undo stuff public Stack<GameState> gameHistory = new(); private const int MAX_UNDO_STEPS = 10; // 10 ходов private const int START_TIME = 450000; // Стартовое время на ход - 7.5 минут private const int MIN_POSSIBLE_TIME = 30000; // Минимальное время на ход - 30 секунд // Конструктор (по умолчанию на стартовых игровых позициях) public Game() { Board = new Board(); // Place totems at starting positions var totem1 = new Totem(1, new Position(0, 0)); var totem2 = new Totem(2, new Position(7, 7)); Player1 = new Player(1, "Wizard1", totem1); Player2 = new Player(2, "Wizard2", totem2); Board.PlaceTotem(totem1); Board.PlaceTotem(totem2); GamePlayerIteration = 0; TurnTime = 450000; } // Основная функция процесса игры public void Run(out Tuple<Tuple<TerrainType, TerrainType>, string> outGameParams) { string winner = ""; bool isGameEnds = false; Tuple<TerrainType, TerrainType> startPlayersTerrains = new (Board.Cells[0][0].Terrain, Board.Cells[7][7].Terrain); //saving state at the start of the mgame SaveCurrentState(GamePlayerIteration); Console.WriteLine("Welcome to Chronicles of Mages!"); while (true) { var currentPlayer = IsPlayer1Turn ? Player1 : Player2; // Check victory if (currentPlayer.Totem.Health <= 0) { winner = IsPlayer1Turn ? Player2.Name : Player1.Name; Console.WriteLine($"{(IsPlayer1Turn ? Player2.Name : Player1.Name)} Wins!"); Player1.TotalScore = IsPlayer1Turn ? -16 + Player1.ExtraScore : 25 + Player1.ExtraScore; Player2.TotalScore = IsPlayer1Turn ? 25 + Player2.ExtraScore : -16 + Player2.ExtraScore; Console.WriteLine($"{Player1.Name} gets {Player1.TotalScore} points"); Console.WriteLine($"{Player2.Name} gets {Player2.TotalScore} points"); // Recording game result SaveManager.RecordGameResult(Player1.Name, Player2.Name, Player1.TotalScore, Player2.TotalScore, // Recording game result SaveManager.GetOptions()); // Блок просмотра истории ходов Console.WriteLine("View the history of game moves? (y/n): "); if (Console.ReadLine()?.Trim().ToLower() == "y") { Console.WriteLine("\n=== History of game moves ==="); Console.WriteLine(); Console.WriteLine($"Start {Player1.Name}'s terrain: {startPlayersTerrains.Item1}"); Console.WriteLine($"Start {Player2.Name}'s terrain: {startPlayersTerrains.Item2}"); HistoryGameMoves.ShowHistoryGameMoves(); } break; } // Краткая инфа о рунах игрока на текущий ход string plHand = ""; foreach(var rune in currentPlayer.Hand) plHand += rune.Type.ToString()[0]; // Строка с информацией о ходе (базовые параметры) var currentMove = new GameMove((GamePlayerIteration+2)/2, currentPlayer.Name, currentPlayer.Mana, plHand, "--", "--", 0, TurnTime); // Отображение времени на ход Console.WriteLine($"\n--- {currentPlayer.Name}'s Turn ---"); Console.WriteLine($"Time for turn: {TurnTime/60000}:{TurnTime%60000/1000:00}"); DateTime runTurnTime = DateTime.Now; // Запуск игрового хода в виде потока var playersTurn = Task.Run(() => Turn(currentPlayer, currentMove, TurnTime, ref isGameEnds)); // Ожидание до окончания времени на ход if (!playersTurn.Wait(TurnTime)) { currentMove.DetailInfo = $"Time is out ({TurnTime/60000}:{TurnTime%60000/1000:00})!"; currentMove.MoveTime = TurnTime; currentPlayer.ExtraScore = Math.Max(currentPlayer.ExtraScore - 2, 0); // Штраф -2 очка (в буфер доп. очков) TerrainType currentTerrainType = Board.Cells[currentPlayer.Totem.Position.Col][currentPlayer.Totem.Position.Row].Terrain; // При нахождении в опасном биоме if (currentTerrainType == TerrainType.Desert || currentTerrainType == TerrainType.Mountain) { currentPlayer.Totem.Health -= 10; currentPlayer.Mana -= 1; } Console.WriteLine("Time is out! The turn passed to another player."); Console.WriteLine("Press any key to continue..."); Console.ReadKey(); } // Проверка на выход из игры if(isGameEnds) { Console.WriteLine("Game over!"); Console.WriteLine("\nPress any key to continue..."); Console.ReadKey(); break; } currentMove.MoveTime = (int)(DateTime.Now - runTurnTime).TotalMilliseconds; //Добавление дополнительных очков AddExtraPoints(currentPlayer, (GamePlayerIteration+2) / 2, runTurnTime, TurnTime); currentPlayer.RegenerateMana(); // Восстановление маны // Добавление руны на каждом 4-м ходу var random = new Random(); var runeTypes = Enum.GetValues<RuneType>(); if ((GamePlayerIteration+2)/2 % 4 == 0 && currentPlayer.Hand.Count < 4) currentPlayer.Hand.Add(new Rune(runeTypes[random.Next(runeTypes.Length)])); // Добавление инфы о ходе в общую историю HistoryGameMoves.GameMoves.Add(currentMove); //saving game afta turn SaveCurrentState(GamePlayerIteration); IsPlayer1Turn = !IsPlayer1Turn; // Смена игрока // Уменьшаем время на следующую итерацию игры if(GamePlayerIteration % 2 == 1) TurnTime = Math.Max(START_TIME/((GamePlayerIteration+4)/2), MIN_POSSIBLE_TIME); GamePlayerIteration++; } outGameParams = new(startPlayersTerrains, winner); } // Игровой ход private void Turn(Player player, GameMove currentMove, int TurnTime, ref bool isGameEnds) { // Рассчитываем время дедлайна на ход DateTime startTurn = DateTime.Now; DateTime turnDeadline = startTurn.AddMilliseconds(TurnTime); // Phase 1: Analysis Console.WriteLine("Phase 1: Analysis"); Board.Display(); Console.WriteLine($"Your Mana: {player.Mana}"); Console.WriteLine("Your Hand: " + string.Join(", ", player.Hand.Select(r => r.Type.ToString()))); Console.WriteLine("Your Totem Health: " + player.Totem.Health); // Pause for analys with timeout if (!TimeoutValidators.WaitForInputWithTimeout("Press any key to continue...", turnDeadline)) { Console.WriteLine("\nTime for analysis phase is over!"); return; } // Phase 2: Action Console.WriteLine("\nPhase 2: Action"); while (DateTime.Now < turnDeadline) { // Меню действий Console.WriteLine("\nChoose action:"); Console.WriteLine("1. Create spell"); Console.WriteLine("2. Move Totem"); Console.WriteLine("3. Undo Last Move"); Console.WriteLine("4. Save Game"); Console.WriteLine("5. Load Game"); Console.WriteLine("6. Quit to Main Menu"); Console.Write("Enter choice (1-6): "); // Reading с дедлайном string? choice = TimeoutValidators.ReadLineWithTimeout(turnDeadline); if (string.IsNullOrEmpty(choice)) { Console.WriteLine("\nTime is out! Automatically passing turn."); return; } choice = choice.Trim(); switch (choice) { case "1": CreateSpell(player, currentMove, turnDeadline); return; case "2": MoveTotem(player, currentMove, turnDeadline); return; case "3": if (UndoLastMove()) { return; //exit turn after undo } continue; //if undo failed stay in menu case "4": SaveGameMenu(); continue; case "5": LoadGameMenu(turnDeadline); return; case "6": isGameEnds = ReturnToMainMenu(); return; default: Console.WriteLine("Please enter a number between 1 and 6."); // UPDATED break; } } Console.WriteLine("\nTurn time expired!"); } // Сохранение текущей игровой позиции private void SaveGameMenu() { Console.WriteLine("\n=== Save Game ==="); Console.Write("Enter save name: "); string saveName = Console.ReadLine() ?? "new_game_saved"; if (string.IsNullOrWhiteSpace(saveName)) { Console.WriteLine("Save cancelled."); return; } SaveManager.SaveGame(this, saveName); } // Загрузка сохранённой игры private void LoadGameMenu(DateTime turnDeadline) { var saveFiles = SaveManager.GetSaveFiles(); // Если файлов нет if (saveFiles.Count == 0) { Console.WriteLine("\nNo save files found."); return; } // Список сохранённых игр в виде меню Console.WriteLine("\n=== Load Game ==="); Console.WriteLine("Available saves:"); for (int i = 0; i < saveFiles.Count; i++) { var save = saveFiles[i]; Console.WriteLine($"{i + 1}. {save.SaveName} ({save.SaveTime:yyyy-MM-dd HH:mm})"); } Console.WriteLine($"{saveFiles.Count + 1}. Cancel"); int choice = SafeReadInt($"\nSelect save (1-{saveFiles.Count + 1}):", 1, saveFiles.Count + 1, turnDeadline); if (choice == saveFiles.Count + 1 || choice == -1) { Console.WriteLine("Load cancelled."); return; } var selectedSave = saveFiles[choice - 1]; Console.Write($"Load '{selectedSave.SaveName}'? (y/n): "); if ((TimeoutValidators.ReadLineWithTimeout(turnDeadline)?.Trim().ToLower() ?? "") != "y") { Console.WriteLine("Load cancelled."); return; } // Установка новых параметров string filePath = selectedSave.FilePath ?? string.Empty; var loadedGame = SaveManager.LoadGame(filePath); if (loadedGame != null) { // Изменение свойств текущей игры this.Board = loadedGame.Board; this.Player1 = loadedGame.Player1; this.Player2 = loadedGame.Player2; this.IsPlayer1Turn = loadedGame.IsPlayer1Turn; Console.WriteLine("\nGame loaded successfully!"); Console.WriteLine($"Current turn: {(IsPlayer1Turn ? Player1.Name : Player2.Name)}"); Console.WriteLine("Press any key to continue..."); Console.ReadKey(); } } // Проверка на согласие завершить игру и вернуться в основное меню private static bool ReturnToMainMenu() { Console.Write("\nReturn to main menu? (y/n): "); if (Console.ReadLine()?.Trim().ToLower() == "y") { Console.WriteLine("Returning to main menu..."); return true; } return false; } // Создание заклинания private void CreateSpell(Player player, GameMove currentMove, DateTime turnDeadline) { // Если нет маны или рун - заклинание сделать невозможно if (player.Hand.Count < 1 || player.Mana < 1) { currentMove.DetailInfo = "Not enough resources!"; Console.WriteLine("Not enough resources!"); return; } // Ввод номеров доступных карт var input = SafeReadIndices( "Enter rune indices to combine (e.g., 0 1 2), space or comma separated:", player.Hand.Count - 1, turnDeadline ); if (input[0] == -1) return; // Если время вышло var selectedRunes = input.Select(i => player.Hand[i]).ToList(); var spell = new Spell(selectedRunes); // Стоимость - 1 ед. маны player.Mana -= 1; foreach (var rune in selectedRunes) { player.Hand.Remove(rune); // Удаление использованных рун } // Выбор цели для нанесения урона int targetRow = SafeReadInt("Enter target row (0-7):", 0, 7, turnDeadline); if (targetRow == -1) return; // Если время вышло int targetCol = SafeReadInt("Enter target col (0-7):", 0, 7, turnDeadline); if (targetCol == -1) return; // Если время вышло var targetPos = new Position(targetRow, targetCol); currentMove.Action = "Create spell"; // Resolution: Damage to totem if present, terrain bonus string messageInfo; var targetCell = Board.Cells[targetRow][targetCol]; if (targetCell.HasTotem && targetCell.PlayerId != player.Id) { // Тотем противника int bonus = 0; if (targetCell.Terrain == GetTerrainForElement(spell.PrimaryElement)) { bonus = 10; // Если руны и биом идеально подходят друг к другу } int totalDamage = spell.Damage + bonus; var opponent = player.Id == 1 ? Player2 : Player1; opponent.Totem.Health -= totalDamage; // Нанесение урона противнику messageInfo = $"Spell '{spell.Name}' deals {totalDamage} damage! Opponent health: {opponent.Totem.Health}"; } else { messageInfo = $"Spell '{spell.Name}' cast on empty terrain, no effect."; } Console.WriteLine(messageInfo); currentMove.DetailInfo = messageInfo; // Потеря 10 очков здоровья и 1 очка маны при нахождении в опасном биоме (пустыня/гора) TerrainType currentTerrainType = Board.Cells[player.Totem.Position.Col][player.Totem.Position.Row].Terrain; if (currentTerrainType == TerrainType.Desert || currentTerrainType == TerrainType.Mountain) { player.Totem.Health -= 10; player.Mana -= 1; } // Добавление 1 новой руны var random = new Random(); var runeTypes = Enum.GetValues<RuneType>(); player.Hand.Add(new Rune(runeTypes[random.Next(runeTypes.Length)])); } // Получение информации о подходящем биоме для типа руны private static TerrainType GetTerrainForElement(RuneType element) { return element switch { RuneType.Fire => TerrainType.Desert, RuneType.Water => TerrainType.Ocean, RuneType.Earth => TerrainType.Forest, RuneType.Air => TerrainType.Mountain, _ => TerrainType.Plain }; } // Перемещение тотема private void MoveTotem(Player player, GameMove currentMove, DateTime turnDeadline) { // Проверка на ману if (player.Mana < 2) { currentMove.DetailInfo = "Not enough mana!"; Console.WriteLine("Not enough mana!"); return; } Position oldPosition = player.Totem.Position; // Выбор новой клетки для перемещения int newRow = SafeReadInt("Enter new row (0-7):", 0, 7, turnDeadline); if (newRow == -1) return; // Если время вышло int newCol = SafeReadInt("Enter new col (0-7):", 0, 7, turnDeadline); if (newCol == -1) return; // Если время вышло var newPos = new Position(newRow, newCol); currentMove.Action = "Move totem"; // Simple validation: within bounds, not occupied if (newRow < 0 || newRow >= 8 || newCol < 0 || newCol >= 8 || Board.Cells[newRow][newCol].HasTotem) { currentMove.DetailInfo = "Invalid move!"; Console.WriteLine("Invalid move!"); return; } // Стоимость - 3 ед. маны player.Mana -= 3; Board.MoveTotem(player.Totem, newPos); // Перемещение тотема TerrainType newTerrainType = Board.Cells[player.Totem.Position.Col][player.Totem.Position.Row].Terrain; currentMove.DetailInfo = $"Totem moved from ({oldPosition.Col}, {oldPosition.Row}) to ({player.Totem.Position.Col}, {player.Totem.Position.Row}):{newTerrainType}"; // Если выбран опасный биом (пустыня или гора) - теряется 5 очков здоровья if (newTerrainType == TerrainType.Desert || newTerrainType == TerrainType.Mountain) { player.Totem.Health -= 5; currentMove.DetailInfo += ": -5 t.health!"; } Console.WriteLine("Totem moved!"); } // Функция добавления дополнительных очков private static void AddExtraPoints(Player player, int gameIteration, DateTime runTurnTime, int turnTime) { int countTimeParts = 1; if (gameIteration <= 3) { return; } // Первые 3 хода - без добавления else if (gameIteration >= 4 && gameIteration <= 7) { countTimeParts = 2; } // Ходы 4-7 - добавление 0 или 1 очка в зависимости от скорости хода (быстрее сделан ход - больше очков) else if (gameIteration >= 8 && gameIteration <= 11) { countTimeParts = 3; } // Ходы 8-11 - добавление 0-2 очков else if (gameIteration >= 12 && gameIteration <= 15) { countTimeParts = 4; } // Ходы 12-15 - добавление 0-3 очков else if (gameIteration >= 16) { countTimeParts = 5; } // Начиная с 16-го хода - добавление 0-4 очков // Расчёт времени на сделанный ход в мс int moveTime = (int)(DateTime.Now - runTurnTime).TotalMilliseconds; if (moveTime > turnTime) { return; } int timePart = turnTime / countTimeParts; player.ExtraScore = Math.Min(player.ExtraScore + (turnTime - moveTime) / timePart, 40); } /* NEW TITLE: ALL of the undo functionality will be there from now!!! */ // Сохранение текущих параметров (для возможной отмены хода в будущем) private void SaveCurrentState(int gameIteration) { var state = new GameState(Board, Player1, Player2, IsPlayer1Turn, gameIteration); gameHistory.Push(state); // Ограничение на размер истории для отмены if (gameHistory.Count > MAX_UNDO_STEPS) { var tempStack = new Stack<GameState>(); for (int i = 0; i < MAX_UNDO_STEPS; i++) { tempStack.Push(gameHistory.Pop()); } gameHistory.Clear(); while (tempStack.Count > 0) { gameHistory.Push(tempStack.Pop()); } } } // Отменить последний ход private bool UndoLastMove() { // Если сделано меньше 1 итерации игры - отмена невозможна if (gameHistory.Count < 2) { Console.WriteLine("\nCannot undo, at least 1 completed turn needed!"); Console.WriteLine("Press any key to continue..."); Console.ReadKey(); return false; } var currentPlayer = IsPlayer1Turn ? Player2 : Player1; Console.WriteLine($"\nUndo move for {currentPlayer.Name}?"); Console.WriteLine("This will revert to previous state. "); Console.Write("Confirm (y/n): "); // Отмена отмены хода if (Console.ReadLine()?.Trim().ToLower() != "y") { Console.WriteLine("Undo cancelled."); Console.WriteLine("Press any key to continue..."); Console.ReadKey(); return false; } // Remove current state gameHistory.Pop(); // Get previous state var previousState = gameHistory.Pop(); // Restore game state Board = previousState.Board; Player1 = previousState.Player1; Player2 = previousState.Player2; IsPlayer1Turn = previousState.IsPlayer1Turn; // Remove last move from history if (HistoryGameMoves.GameMoves.Count >= 1) { HistoryGameMoves.GameMoves.RemoveAt(HistoryGameMoves.GameMoves.Count - 1); } Console.WriteLine("\nMove undone successfully!"); Console.WriteLine($"Current turn: {(IsPlayer1Turn ? Player2.Name : Player1.Name)}"); Console.WriteLine($"Available undo steps: {gameHistory.Count}"); Console.WriteLine("Press any key to continue..."); Console.ReadKey(); return true; } /** So, we are doing some exception handling down here. Just making sure that every input is al'ight and we are good to go uk.. **/ // Функция для ввода числа с консоли с ограничением по времени (даётся сообщение на вход) private static int SafeReadInt(string prompt, int min, int max, DateTime turnDeadline = default) { while (true) { Console.WriteLine(prompt); //if (turnDeadline == default(DateTime)) var input = (turnDeadline == default(DateTime)) ? Console.ReadLine() : TimeoutValidators.ReadLineWithTimeout(turnDeadline); if (input == null) return -1; // Если время на ввод истекло // Проверка на корректность if (int.TryParse(input, out int value)) { if (value >= min && value <= max) { return value; } else { Console.WriteLine($"Please enter a number between {min} and {max}."); } } else { Console.WriteLine("Invalid input. Please enter a valid number."); } } } // Функция ввода числел с консоли с ограничением по времени (для создания заклинания с помощью рун; даётся сообщение на вход) private static int[] SafeReadIndices(string prompt, int maxIndex, DateTime turnDeadline) { while (true) { Console.WriteLine(prompt); var input = TimeoutValidators.ReadLineWithTimeout(turnDeadline); if (input == null) return [-1]; // Если время на ввод истекло _ = input.Trim(); if (string.IsNullOrEmpty(input)) { Console.WriteLine("You must enter at least one index."); continue; } var parts = input.Split([' ', ','], StringSplitOptions.RemoveEmptyEntries); var indices = new List<int>(); // Проверка на корректность foreach (var part in parts) { if (int.TryParse(part, out int idx)) { if (idx >= 0 && idx <= maxIndex) { indices.Add(idx); } else { Console.WriteLine($"Index {part} is out of range (0–{maxIndex})."); indices.Clear(); break; } } else { Console.WriteLine($"'{part}' is not a valid number."); indices.Clear(); break; } } if (indices.Count > 0) { // Remove duplicates and sort (optional) return [.. indices.Distinct().OrderBy(x => x)]; } } } // Проверка на корректность указанного имени игрока public static string ValidatePlayerName(string? playerName) { while (true) { if (string.IsNullOrEmpty(playerName) || playerName.StartsWith(' ') || playerName.Length > 25) { Console.WriteLine("Your name is empty or incorrect or too large (25+). Try again, please!"); playerName = Console.ReadLine(); continue; } break; } return playerName; } // Yep) I was here)) } // Hardest game start ever imagened! (this main is sooo big =^.^= (UPD.: now it's fr big)) class Program { static void Main() { ShowMainMenu(); } // GAME MAIN MENU public static void ShowMainMenu() { Console.Clear(); Console.WriteLine("═══════════════════════════════"); Console.WriteLine(" CHRONICLES OF MAGES"); Console.WriteLine("═══════════════════════════════"); Console.WriteLine(); Console.WriteLine("1. New Game"); Console.WriteLine("2. Load Game"); Console.WriteLine("3. Manage Saves"); Console.WriteLine("4. Show Records"); Console.WriteLine("5. Show Games Results"); Console.WriteLine("6. Exit"); Console.WriteLine(); Console.Write("Select option (1-6): "); var choice = Console.ReadLine(); switch (choice) { case "1": StartNewGame(); break; case "2": LoadGameFromMenu(); break; case "3": ManageSaves(); break; case "4": ShowRecords(); break; case "5": ShowGamesResults(); break; case "6": Console.WriteLine("\nThanks for playing!"); return; default: Console.WriteLine("\nInvalid choice. Please try again."); Console.WriteLine("Press any key to continue..."); Console.ReadKey(); ShowMainMenu(); break; } } // Запуск новой игры static void StartNewGame() { Console.Clear(); Console.WriteLine("Starting new game...\n"); var game = new Game(); // Установка имён игроков Console.Write("Player 1, enter your name: "); var player1Name = Console.ReadLine(); game.Player1.Name = Game.ValidatePlayerName(player1Name); Console.Write("Player 2, enter your name: "); var player2Name = Console.ReadLine(); game.Player2.Name = Game.ValidatePlayerName(player2Name); if (game.Player2.Name == game.Player1.Name) // При совпадении имён добавляем "2" ко 2-му game.Player2.Name += "2"; Console.WriteLine("The names have been successfully set, the game begins!"); Console.ReadKey(); // Pause // Запуск игры! game.Run(out Tuple<Tuple<TerrainType, TerrainType>, string> outGameParams); // Блок сохранения истории ходов в текстовый файл Console.WriteLine("Save the history of game moves in file? (y/n): "); if (Console.ReadLine()?.Trim().ToLower() == "y") SaveManager.SaveHistoryGameMoves(game.Player1.Name, game.Player2.Name, game.Player1.TotalScore, game.Player2.TotalScore, outGameParams); HistoryGameMoves.GameMoves.Clear(); // Очистка истории для новых игр Console.WriteLine("\nPress any key to continue..."); Console.ReadKey(); ShowMainMenu(); } // Загрузка сохранённой игры через меню static void LoadGameFromMenu() { var saveFiles = SaveManager.GetSaveFiles(); if (saveFiles.Count == 0) { Console.WriteLine("\nNo save files found."); Console.WriteLine("Press any key to return to main menu..."); Console.ReadKey(); ShowMainMenu(); return; } // Список всех сохранений в виде меню Console.WriteLine("\n=== Load Game ==="); Console.WriteLine("Available saves:"); for (int i = 0; i < saveFiles.Count; i++) { var save = saveFiles[i]; Console.WriteLine($"{i + 1}. {save.SaveName} ({save.SaveTime:yyyy-MM-dd HH:mm})"); } Console.WriteLine($"{saveFiles.Count + 1}. Back to Main Menu"); int choice = SafeReadInt($"\nSelect save (1-{saveFiles.Count + 1}):", 1, saveFiles.Count + 1); if (choice == saveFiles.Count + 1) { ShowMainMenu(); return; } var selectedSave = saveFiles[choice - 1]; // Установка параметров для продолжения старой игры string filePath = selectedSave.FilePath ?? string.Empty; var loadedGame = SaveManager.LoadGame(filePath); // Запуск сохранения в случае успеха if (loadedGame != null) { Console.WriteLine("\nGame loaded successfully!"); Console.WriteLine("Press any key to start playing..."); Console.ReadKey(); loadedGame.Run(out Tuple<Tuple<TerrainType, TerrainType>, string> outGameParams); // Блок сохранения истории ходов в текстовый файл Console.WriteLine("Save the history of game moves in file? (y/n): "); if (Console.ReadLine()?.Trim().ToLower() == "y") SaveManager.SaveHistoryGameMoves(loadedGame.Player1.Name, loadedGame.Player2.Name, loadedGame.Player1.TotalScore, loadedGame.Player2.TotalScore, outGameParams); HistoryGameMoves.GameMoves.Clear(); // Очистка истории для новых игр } else { Console.WriteLine("\nFailed to load game."); } Console.WriteLine("Press any key to return to main menu..."); Console.ReadKey(); ShowMainMenu(); } // Блок управления файлами сохранённых игр static void ManageSaves() { var saveFiles = SaveManager.GetSaveFiles(); Console.WriteLine("\n=== Manage Save Files ==="); if (saveFiles.Count == 0) // Нет файлов { Console.WriteLine("No save files found."); } else { // Список информации о файлах Console.WriteLine("Available saves:"); for (int i = 0; i < saveFiles.Count; i++) { var save = saveFiles[i]; string filePath = save.FilePath ?? string.Empty; Console.WriteLine($"{i + 1}. {save.SaveName}"); Console.WriteLine($" File: {save.FileName}"); Console.WriteLine($" Saved: {save.SaveTime:yyyy-MM-dd HH:mm}"); Console.WriteLine($" Size: {new FileInfo(filePath).Length / 1024} KB"); Console.WriteLine(); } Console.WriteLine($"{saveFiles.Count + 1}. Delete a save"); Console.WriteLine($"{saveFiles.Count + 2}. Back to Main Menu"); int choice = SafeReadInt($"\nSelect option (1-{saveFiles.Count + 2}):", 1, saveFiles.Count + 2); if (choice == saveFiles.Count + 1) { // Delete save int deleteChoice = SafeReadInt($"Select save to delete (1-{saveFiles.Count}):", 1, saveFiles.Count); var saveToDelete = saveFiles[deleteChoice - 1]; Console.Write($"\nAre you sure you want to delete '{saveToDelete.SaveName}'? (y/n): "); if (Console.ReadLine()?.Trim().ToLower() == "y") { string filePath = saveToDelete.FilePath ?? string.Empty; SaveManager.DeleteSave(filePath); } } else if (choice == saveFiles.Count + 2) { ShowMainMenu(); return; } } Console.WriteLine("\nPress any key to continue..."); Console.ReadKey(); ShowMainMenu(); } // Отображение таблицы лучших игроков static void ShowRecords() { // Определение пути к файлу результатов string filePath = SaveManager.GetFilePath("games_results.json"); if (!File.Exists(filePath)) { Console.WriteLine("Results file not found."); return; } string jsonContent = File.ReadAllText(filePath); try { // Полный список всех исходов игр (имена игроков и их очки) var games = JsonSerializer.Deserialize<List<GameResults>>(jsonContent); if(games != null && games.Count != 0) { // Разбиение по каждому игроку отдельно в новый список List<(string PlayerName, int Points)> playersInfo = []; foreach(var gameResult in games) { playersInfo.Add((gameResult.P1Name, gameResult.P1GamePoints)); playersInfo.Add((gameResult.P2Name, gameResult.P2GamePoints)); } // Формирование списка из игроков по убыванию очков var topPlayersList = playersInfo .GroupBy(pl => pl.PlayerName) .Select(currentPlayer => new { PlayerName = currentPlayer.Key, SumPoints = currentPlayer.Sum(pl => pl.Points), CountTotalWins = currentPlayer.Count(pl => pl.Points >= 25), // Минимально возможный счёт для победителя - 25 CountTotalDefeats = currentPlayer.Count(pl => pl.Points <= 24), // Максимально возможный счёт для проигравшего - 24 CountOfGames = currentPlayer.Count() }) .OrderByDescending(pl => pl.SumPoints); // Отображение ТОП-10 лучших игроков GetPaginatedList(1, topPlayersList); Console.WriteLine($"Count of players: {topPlayersList.Count()}"); bool isShowPages = true; // Переход между другими страницами / возвращение в меню while(isShowPages) { Console.WriteLine("1. Open other page"); Console.WriteLine("2. Back to Main Menu"); Console.WriteLine(); Console.Write("Select option (1 or 2): "); var choice = Console.ReadLine(); switch (choice) { case "1": int page = SafeReadInt($"Enter new page (1-{topPlayersList.Count()/10 + 1}): ", 1, topPlayersList.Count()/10 + 1); GetPaginatedList(page, topPlayersList); Console.WriteLine("Press any key to continue..."); Console.ReadKey(); break; case "2": isShowPages = false; break; default: Console.WriteLine("\nInvalid choice. Please try again."); Console.WriteLine("Press any key to continue..."); Console.ReadKey(); break; } } } } catch(JsonException ex) // Обработка ошибок { Console.WriteLine($"Error of reading file: {ex.Message}"); } Console.WriteLine("\nPress any key to continue..."); Console.ReadKey(); ShowMainMenu(); } // Получение списка лучших игроков по номеру страницы топа static void GetPaginatedList(int page, IEnumerable<dynamic> topPlayersList) { var paginatedPlayersList = topPlayersList.Skip(10 * (page - 1)).Take(10); // По 10 игроков на страницу Console.WriteLine($"\n=== TOP PLAYERS ({(page - 1) * 10 + 1}-{page * 10}) ==="); Console.WriteLine(new string('=', 69)); Console.WriteLine("| {0,-3} | {1,-26} | {2,-5} | {3,-4} | {4,-7} | {5,-5} |", "№", "Player", "Games", "Wins", "Defeats", "Score"); Console.WriteLine(new string('=', 69)); int count = (page - 1) * 10; foreach (var currentPlayer in paginatedPlayersList) { count++; Console.WriteLine("| {0,-3} | {1,-26} | {2,-5} | {3,-4} | {4,-7} | {5,-5} |", count, currentPlayer.PlayerName, currentPlayer.CountOfGames, currentPlayer.CountTotalWins, currentPlayer.CountTotalDefeats, currentPlayer.SumPoints ); } Console.WriteLine(new string('=', 69)); Console.WriteLine(); Console.WriteLine($"Current page {page}"); // Текущая страница } // Отображение таблицы результатов всех игр static void ShowGamesResults() { // Определение пути к файлу результатов string filePath = SaveManager.GetFilePath("games_results.json"); if (!File.Exists(filePath)) { Console.WriteLine("Results file not found."); return; } string jsonContent = File.ReadAllText(filePath); try { // Полный список всех исходов игр (имена игроков и их очки) var games = JsonSerializer.Deserialize<List<GameResults>>(jsonContent); // Вывод таблицы if(games != null && games.Count != 0) { Console.WriteLine("\n=== GAMES RESULTS ==="); Console.WriteLine(new string('=', 125)); Console.WriteLine("| {0,-4} | {1,-54} | {2,-8} | {3,-26} | {4,-17} |", "№", "Magical Rivals", "Points", "Winner", "Date & time"); Console.WriteLine(new string('=', 125)); int count = 0; foreach(var gameResult in games) { count++; int maxGamePoints = Math.Max(gameResult.P1GamePoints, gameResult.P2GamePoints); string winner = maxGamePoints == gameResult.P1GamePoints? gameResult.P1Name : gameResult.P2Name; Console.WriteLine( "| {0,-4} | {1,-54} | {2,-8} | {3,-26} | {4,-17:dd.MM.yyyy HH:mm} |", count, $"{gameResult.P1Name} VS {gameResult.P2Name}", $"{gameResult.P1GamePoints} - {gameResult.P2GamePoints}", winner, gameResult.GameDate ); } Console.WriteLine(new string('=', 125)); } } catch(JsonException ex) // Обработка ошибок { Console.WriteLine($"Error of reading file: {ex.Message}"); } Console.WriteLine("\nPress any key to continue..."); Console.ReadKey(); ShowMainMenu(); } // Функция для ввода числа с консоли (даётся сообщение на вход) static int SafeReadInt(string prompt, int min, int max) { while (true) { Console.Write(prompt); var input = Console.ReadLine(); // Проверка на корректность if (int.TryParse(input, out int value)) { if (value >= min && value <= max) { return value; } else { Console.WriteLine($"Please enter a number between {min} and {max}."); } } else { Console.WriteLine("Invalid input. Please enter a valid number."); } } } } }