/
anstyfil
/
Minesweeper
Обзор
Документация
Войти
/
anstyfil
/
Minesweeper
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
Storage/GameLoader.cs
215 строк
9 KB
Geyer Daria
restore move history when loading game
18 дек 2025, 15:36
18 дек 2025, 15:36
b66b321
Код
Авторство
О чём код?
using System; using System.IO; using System.Linq; using System.Text.Json; using Minesweeper.Game; namespace Minesweeper.Storage { /// <summary> /// Утилитарный класс для загрузки состояния игры из JSON-файла. /// </summary> public static class GameLoader { /// <summary> /// Вспомогательный класс для десериализации JSON. /// </summary> private class SavedGameData { public int Width { get; set; } public int Height { get; set; } public int MineCount { get; set; } public int CurrentPlayer { get; set; } public int Score1 { get; set; } public int Score2 { get; set; } public bool GameOver { get; set; } public int Winner { get; set; } public bool IsFirstMove { get; set; } = true; public string Player1Name { get; set; } = string.Empty; public string Player2Name { get; set; } = string.Empty; public SavedCellData[] Cells { get; set; } = Array.Empty<SavedCellData>(); public MoveHistory[]? MoveHistory { get; set; } } /// <summary> /// Вспомогательный класс для десериализации данных клетки. /// </summary> private class SavedCellData { public bool IsMine { get; set; } public bool IsRevealed { get; set; } } /// <summary> /// Загружает состояние игры из JSON-файла. /// </summary> /// <param name="filePath">Путь к файлу для загрузки</param> /// <returns>Восстановленное состояние игры или null, если загрузка не удалась</returns> /// <exception cref="ArgumentNullException">Если filePath равен null</exception> /// <exception cref="ArgumentException">Если filePath пустой</exception> public static BoardState? Load(string filePath) { if (filePath == null) { throw new ArgumentNullException(nameof(filePath)); } if (string.IsNullOrWhiteSpace(filePath)) { throw new ArgumentException("Путь к файлу не может быть пустым", nameof(filePath)); } if (!File.Exists(filePath)) { return null; } try { // Читаем и десериализуем JSON string jsonContent = File.ReadAllText(filePath); SavedGameData? savedData = JsonSerializer.Deserialize<SavedGameData>(jsonContent); if (savedData == null) { return null; } // Валидация размеров поля if (savedData.Width <= 0 || savedData.Height <= 0) { return null; } int expectedCellCount = savedData.Width * savedData.Height; // Валидация количества клеток if (savedData.Cells == null || savedData.Cells.Length != expectedCellCount) { return null; } // Валидация количества мин int actualMineCount = savedData.Cells.Count(c => c.IsMine); if (actualMineCount != savedData.MineCount) { return null; } // Валидация: нет открытых мин bool hasRevealedMine = savedData.Cells.Any(c => c.IsMine && c.IsRevealed); if (hasRevealedMine) { return null; } // Валидация: сумма очков равна числу открытых клеток int revealedCount = savedData.Cells.Count(c => c.IsRevealed); int totalScore = savedData.Score1 + savedData.Score2; if (totalScore != revealedCount) { return null; } // Создаём GameConfig и BoardGeometry var config = new GameConfig(savedData.Width, savedData.Height, savedData.MineCount); var geometry = new BoardGeometry(config); // Создаём BoardState var boardState = new BoardState(geometry); // Восстанавливаем состояние клеток for (int i = 0; i < savedData.Cells.Length; i++) { ref Cell cell = ref boardState.AtIndex(i); cell.IsMine = savedData.Cells[i].IsMine; cell.IsRevealed = savedData.Cells[i].IsRevealed; } // Пересчитываем AdjacentMines для всех клеток for (int y = 0; y < geometry.Height; y++) { for (int x = 0; x < geometry.Width; x++) { ref Cell cell = ref boardState.At(x, y); // Для клеток с миной AdjacentMines не важен, но можно оставить 0 if (cell.IsMine) { continue; } // Считаем количество мин среди 8 соседей int adjacentMineCount = 0; for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { if (dx == 0 && dy == 0) { continue; } int nx = x + dx; int ny = y + dy; if (geometry.IsInside(nx, ny)) { if (boardState.At(nx, ny).IsMine) { adjacentMineCount++; } } } } cell.AdjacentMines = adjacentMineCount; } } // Восстанавливаем состояние игры boardState.CurrentPlayer = savedData.CurrentPlayer; boardState.Score1 = savedData.Score1; boardState.Score2 = savedData.Score2; boardState.GameOver = savedData.GameOver; boardState.Winner = savedData.Winner; boardState.IsFirstMove = savedData.IsFirstMove; boardState.Player1Name = savedData.Player1Name ?? string.Empty; boardState.Player2Name = savedData.Player2Name ?? string.Empty; // Валидация: если IsFirstMove = true, но есть открытые клетки - это противоречие bool hasRevealedCells = savedData.Cells.Any(c => c.IsRevealed); if (boardState.IsFirstMove && hasRevealedCells) { // Автоматически исправляем противоречие boardState.MarkFirstMoveCompleted(); } // Восстанавливаем историю ходов if (savedData.MoveHistory != null && savedData.MoveHistory.Length > 0) { // Очищаем историю в board (на случай, если она была инициализирована) boardState.ClearHistory(); // Добавляем все ходы из сохраненной истории foreach (var move in savedData.MoveHistory) { boardState.AddMove(move); } } return boardState; } catch (JsonException) { // Некорректный JSON return null; } catch (Exception) { // Другие ошибки (например, ArgumentException от GameConfig) return null; } } } }