[Fix] Rename Scripts Folder

This commit is contained in:
2026-06-07 00:30:10 +07:00
parent 79a928ae52
commit 6c9cdaf67d
140 changed files with 0 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
namespace Minesweeper.Core
{
public readonly struct BoardActionResult
{
public BoardActionResult(bool changed, bool hitMine, bool won, bool invalid)
{
Changed = changed;
HitMine = hitMine;
Won = won;
Invalid = invalid;
}
public bool Changed { get; }
public bool HitMine { get; }
public bool Won { get; }
public bool Invalid { get; }
public static BoardActionResult NoChange => new BoardActionResult(false, false, false, false);
public static BoardActionResult InvalidAction => new BoardActionResult(false, false, false, true);
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7457e882fbca4e644959ccff78510436
+23
View File
@@ -0,0 +1,23 @@
namespace Minesweeper.Core
{
public readonly struct BoardCellData
{
public BoardCellData(int x, int y, bool isMine, bool isOpened, bool isFlagged, int neighborMines)
{
X = x;
Y = y;
IsMine = isMine;
IsOpened = isOpened;
IsFlagged = isFlagged;
NeighborMines = neighborMines;
}
public int X { get; }
public int Y { get; }
public bool IsMine { get; }
public bool IsOpened { get; }
public bool IsFlagged { get; }
public int NeighborMines { get; }
public string DisplayValue => IsMine ? "M" : NeighborMines.ToString();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 832de90f32c3d7d4d8526f82eb203866
+309
View File
@@ -0,0 +1,309 @@
using System;
using System.Collections.Generic;
namespace Minesweeper.Core
{
public sealed class BoardService : IBoardService
{
private readonly IGameSettingsService settingsService;
private readonly Random random = new Random();
private CellData[,] cells;
public BoardService(IGameSettingsService settingsService)
{
this.settingsService = settingsService;
}
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 int OpenedSafeCellsCount { get; private set; }
public int SafeCellsCount => Width * Height - MinesCount;
public void InitializeEmptyBoard()
{
Width = settingsService.SizeX;
Height = settingsService.SizeY;
MinesCount = Math.Min(settingsService.MinesCount, Width * Height - 1);
OpenedSafeCellsCount = 0;
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();
IsGenerated = true;
return true;
}
public BoardActionResult OpenCell(int x, int y)
{
EnsureInitialized();
if (!IsGenerated || !IsInside(x, y))
{
return BoardActionResult.InvalidAction;
}
var cell = cells[x, y];
if (cell.IsOpened || cell.IsFlagged)
{
return BoardActionResult.NoChange;
}
if (cell.IsMine)
{
cell.IsOpened = true;
return new BoardActionResult(true, true, false, false);
}
if (cell.NeighborMines == 0)
{
RevealEmptyArea(x, y);
}
else
{
OpenSafeCell(cell);
}
return new BoardActionResult(true, false, IsWin(), false);
}
public BoardActionResult ToggleFlag(int x, int y)
{
EnsureInitialized();
if (!IsInside(x, y))
{
return BoardActionResult.InvalidAction;
}
var cell = cells[x, y];
if (cell.IsOpened)
{
return BoardActionResult.NoChange;
}
cell.IsFlagged = !cell.IsFlagged;
return new BoardActionResult(true, false, false, false);
}
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<BoardCellData> GetCells()
{
EnsureInitialized();
var result = new List<BoardCellData>(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 EnsureInitialized()
{
if (cells == null)
{
InitializeEmptyBoard();
}
}
private void PlaceMines(int safeX, int safeY)
{
var positions = new List<int>(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<int> 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 void RevealEmptyArea(int startX, int startY)
{
var visited = new bool[Width, Height];
var queue = new Queue<CellData>();
queue.Enqueue(cells[startX, startY]);
while (queue.Count > 0)
{
var cell = queue.Dequeue();
if (visited[cell.X, cell.Y] || cell.IsMine || cell.IsFlagged)
{
continue;
}
visited[cell.X, cell.Y] = true;
OpenSafeCell(cell);
if (cell.NeighborMines != 0)
{
continue;
}
for (var x = cell.X - 1; x <= cell.X + 1; x++)
{
for (var y = cell.Y - 1; y <= cell.Y + 1; y++)
{
if ((x == cell.X && y == cell.Y) || !IsInside(x, y))
{
continue;
}
var neighbor = cells[x, y];
if (!visited[x, y] && !neighbor.IsMine && !neighbor.IsFlagged && !neighbor.IsOpened)
{
queue.Enqueue(neighbor);
}
}
}
}
}
private void OpenSafeCell(CellData cell)
{
if (cell.IsOpened || cell.IsMine)
{
return;
}
cell.IsOpened = true;
OpenedSafeCellsCount++;
}
private bool IsWin()
{
return OpenedSafeCellsCount >= SafeCellsCount;
}
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; }
}
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 11dec36e720c3e745b9dc11d588b5794
+33
View File
@@ -0,0 +1,33 @@
using System;
namespace Minesweeper.Core
{
public sealed class GamePauseService : IGamePauseService
{
public event Action<bool> PauseChanged;
public bool IsPaused { get; private set; }
public void Pause()
{
if (IsPaused)
{
return;
}
IsPaused = true;
PauseChanged?.Invoke(IsPaused);
}
public void Resume()
{
if (!IsPaused)
{
return;
}
IsPaused = false;
PauseChanged?.Invoke(IsPaused);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 40441c28481279147959eafabd8a032a
@@ -0,0 +1,81 @@
using Minesweeper.Config;
using UnityEngine;
namespace Minesweeper.Core
{
public sealed class GameSettingsService : IGameSettingsService
{
private const int MinimumMinesCount = 1;
private readonly MinesweeperGameConfig config;
private readonly IGameSettingsStorage storage;
private GameSettingsValue current;
public GameSettingsService(MinesweeperGameConfig config, IGameSettingsStorage storage)
{
this.config = config;
this.storage = storage;
current = LoadInitialSettings();
}
public int SizeX => current.SizeX;
public int SizeY => current.SizeY;
public int MinesCount => current.MinesCount;
public GameSettingsValue Current => current;
public GameSettingsValue Clamp(GameSettingsValue value)
{
var sizeX = Mathf.Clamp(value.SizeX, config.MinSizeX, config.MaxSizeX);
var sizeY = Mathf.Clamp(value.SizeY, config.MinSizeY, config.MaxSizeY);
EnsureAtLeastTwoCells(ref sizeX, ref sizeY);
var mines = Mathf.Clamp(value.MinesCount, MinimumMinesCount, GetMaxMines(sizeX, sizeY));
return new GameSettingsValue(sizeX, sizeY, mines);
}
public bool ApplyAndSaveIfChanged(GameSettingsValue value)
{
var clamped = Clamp(value);
if (current.Equals(clamped))
{
return false;
}
current = clamped;
storage.Save(current);
return true;
}
public int GetMaxMines(int sizeX, int sizeY)
{
return Mathf.Max(MinimumMinesCount, sizeX * sizeY - 1);
}
private GameSettingsValue LoadInitialSettings()
{
if (storage.TryLoad(out var saved))
{
return Clamp(saved);
}
return Clamp(new GameSettingsValue(config.MinSizeX, config.MinSizeY, MinimumMinesCount));
}
private void EnsureAtLeastTwoCells(ref int sizeX, ref int sizeY)
{
if (sizeX * sizeY > 1)
{
return;
}
if (sizeX < config.MaxSizeX)
{
sizeX++;
}
else if (sizeY < config.MaxSizeY)
{
sizeY++;
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 38b7be3d055b5c34bbba9b36c0e11fc6
+21
View File
@@ -0,0 +1,21 @@
namespace Minesweeper.Core
{
public readonly struct GameSettingsValue
{
public GameSettingsValue(int sizeX, int sizeY, int minesCount)
{
SizeX = sizeX;
SizeY = sizeY;
MinesCount = minesCount;
}
public int SizeX { get; }
public int SizeY { get; }
public int MinesCount { get; }
public bool Equals(GameSettingsValue other)
{
return SizeX == other.SizeX && SizeY == other.SizeY && MinesCount == other.MinesCount;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5a48645a1753ceb4ba0401ec44d70070
+11
View File
@@ -0,0 +1,11 @@
namespace Minesweeper.Core
{
public enum GameState
{
FieldSelection,
Preparing,
Playing,
Lost,
Won
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c06ebf54d6bacdf4888fabbf29bea1cd
+27
View File
@@ -0,0 +1,27 @@
using System;
namespace Minesweeper.Core
{
public sealed class GameStateService : IGameStateService
{
public event Action<GameState> StateChanged;
public GameState Current { get; private set; } = GameState.FieldSelection;
public void SetState(GameState state)
{
if (Current == state)
{
return;
}
Current = state;
StateChanged?.Invoke(Current);
}
public void NotifyCurrentStateChanged()
{
StateChanged?.Invoke(Current);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cf69805439993c14887ea7bb9b15bd02
+48
View File
@@ -0,0 +1,48 @@
using System;
using UnityEngine;
using VContainer.Unity;
namespace Minesweeper.Core
{
public sealed class GameTimerService : IGameTimerService, ITickable
{
private readonly IGamePauseService pauseService;
private readonly IGameStateService gameStateService;
private int lastReportedSeconds = -1;
public GameTimerService(IGameStateService gameStateService, IGamePauseService pauseService)
{
this.gameStateService = gameStateService;
this.pauseService = pauseService;
}
public event Action<float> TimeChanged;
public float ElapsedSeconds { get; private set; }
public void Tick()
{
if (gameStateService.Current != GameState.Playing || pauseService.IsPaused)
{
return;
}
ElapsedSeconds += Time.deltaTime;
var seconds = Mathf.FloorToInt(ElapsedSeconds);
if (seconds == lastReportedSeconds)
{
return;
}
lastReportedSeconds = seconds;
TimeChanged?.Invoke(ElapsedSeconds);
}
public void Reset()
{
ElapsedSeconds = 0f;
lastReportedSeconds = -1;
TimeChanged?.Invoke(ElapsedSeconds);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ed8262be24a32a04abfd5bc5ec8544bb
+22
View File
@@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace Minesweeper.Core
{
public interface IBoardService
{
int Width { get; }
int Height { get; }
int MinesCount { get; }
bool IsGenerated { get; }
int OpenedSafeCellsCount { get; }
int SafeCellsCount { get; }
void InitializeEmptyBoard();
bool GenerateAfterFirstClick(int safeX, int safeY);
BoardActionResult OpenCell(int x, int y);
BoardActionResult ToggleFlag(int x, int y);
bool IsInside(int x, int y);
bool TryGetCell(int x, int y, out BoardCellData cell);
IReadOnlyList<BoardCellData> GetCells();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1339a8a92f1f56d4fa786ce011294737
+14
View File
@@ -0,0 +1,14 @@
using System;
namespace Minesweeper.Core
{
public interface IGamePauseService
{
event Action<bool> PauseChanged;
bool IsPaused { get; }
void Pause();
void Resume();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 82dfb9fe1e7004f4f88df366f8e76b2d
@@ -0,0 +1,14 @@
namespace Minesweeper.Core
{
public interface IGameSettingsService
{
int SizeX { get; }
int SizeY { get; }
int MinesCount { get; }
GameSettingsValue Current { get; }
GameSettingsValue Clamp(GameSettingsValue value);
bool ApplyAndSaveIfChanged(GameSettingsValue value);
int GetMaxMines(int sizeX, int sizeY);
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3dcff3a940f664b4b96722f28e46636d
@@ -0,0 +1,8 @@
namespace Minesweeper.Core
{
public interface IGameSettingsStorage
{
bool TryLoad(out GameSettingsValue value);
void Save(GameSettingsValue value);
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 54d04c5398018bc418e1a2528c559d0a
+14
View File
@@ -0,0 +1,14 @@
using System;
namespace Minesweeper.Core
{
public interface IGameStateService
{
event Action<GameState> StateChanged;
GameState Current { get; }
void SetState(GameState state);
void NotifyCurrentStateChanged();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7aea04a8c0e8d3a4e8d991a4348430db
+13
View File
@@ -0,0 +1,13 @@
using System;
namespace Minesweeper.Core
{
public interface IGameTimerService
{
event Action<float> TimeChanged;
float ElapsedSeconds { get; }
void Reset();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 61242d395cb1d974daffd9e0815ec34c