using System; using System.Collections.Generic; using Minesweeper.Config; namespace Minesweeper.Core { public sealed class BoardService : IBoardService { private const int DefaultWidth = 9; private const int DefaultHeight = 9; private const int DefaultMinesCount = 10; private readonly MinesweeperGameConfig config; private readonly Random random = new Random(); private CellData[,] cells; public BoardService(MinesweeperGameConfig config) { this.config = config; } public int Width { get; private set; } public int Height { get; private set; } public int MinesCount { get; private set; } public bool IsGenerated { get; private set; } public void InitializeEmptyBoard() { ResolveConfig(out var width, out var height, out var minesCount); Width = width; Height = height; MinesCount = minesCount; IsGenerated = false; cells = new CellData[Width, Height]; for (var x = 0; x < Width; x++) { for (var y = 0; y < Height; y++) { cells[x, y] = new CellData(x, y); } } } public bool GenerateAfterFirstClick(int safeX, int safeY) { EnsureInitialized(); if (IsGenerated || !IsInside(safeX, safeY)) { return false; } PlaceMines(safeX, safeY); CalculateNeighborMines(); cells[safeX, safeY].IsOpened = true; IsGenerated = true; return true; } public bool IsInside(int x, int y) { return cells != null && x >= 0 && y >= 0 && x < Width && y < Height; } public bool TryGetCell(int x, int y, out BoardCellData cell) { if (!IsInside(x, y)) { cell = default; return false; } cell = ToData(cells[x, y]); return true; } public IReadOnlyList GetCells() { EnsureInitialized(); var result = new List(Width * Height); for (var y = 0; y < Height; y++) { for (var x = 0; x < Width; x++) { result.Add(ToData(cells[x, y])); } } return result; } private void ResolveConfig(out int width, out int height, out int minesCount) { width = config.Width; height = config.Height; minesCount = config.MinesCount; if (width <= 0 || height <= 0 || minesCount <= 0 || minesCount >= width * height) { width = DefaultWidth; height = DefaultHeight; minesCount = DefaultMinesCount; } } private void EnsureInitialized() { if (cells == null) { InitializeEmptyBoard(); } } private void PlaceMines(int safeX, int safeY) { var positions = new List(Width * Height - 1); for (var x = 0; x < Width; x++) { for (var y = 0; y < Height; y++) { if (x == safeX && y == safeY) { continue; } positions.Add(ToIndex(x, y)); } } Shuffle(positions); for (var i = 0; i < MinesCount; i++) { var index = positions[i]; var x = index % Width; var y = index / Width; cells[x, y].IsMine = true; } } private void Shuffle(List values) { for (var i = values.Count - 1; i > 0; i--) { var j = random.Next(i + 1); (values[i], values[j]) = (values[j], values[i]); } } private void CalculateNeighborMines() { for (var x = 0; x < Width; x++) { for (var y = 0; y < Height; y++) { if (cells[x, y].IsMine) { cells[x, y].NeighborMines = 0; continue; } cells[x, y].NeighborMines = CountNeighborMines(x, y); } } } private int CountNeighborMines(int centerX, int centerY) { var count = 0; for (var x = centerX - 1; x <= centerX + 1; x++) { for (var y = centerY - 1; y <= centerY + 1; y++) { if (x == centerX && y == centerY) { continue; } if (IsInside(x, y) && cells[x, y].IsMine) { count++; } } } return count; } private int ToIndex(int x, int y) { return y * Width + x; } private static BoardCellData ToData(CellData cell) { return new BoardCellData(cell.X, cell.Y, cell.IsMine, cell.IsOpened, cell.IsFlagged, cell.NeighborMines); } private sealed class CellData { public CellData(int x, int y) { X = x; Y = y; } public int X { get; } public int Y { get; } public bool IsMine { get; set; } public bool IsOpened { get; set; } public bool IsFlagged { get; set; } public int NeighborMines { get; set; } } } }