[Add] Playful interaction with cells

This commit is contained in:
2026-06-06 21:34:35 +07:00
parent 1a6f8901a2
commit 1483964eaf
5 changed files with 195 additions and 5 deletions
@@ -55,26 +55,78 @@ namespace Minesweeper.Commands
public void Handle(OpenCellCommand command)
{
if (gameStateService.Current != GameState.Preparing)
var state = gameStateService.Current;
if (state != GameState.Preparing && state != GameState.Playing)
{
return;
}
if (!boardService.GenerateAfterFirstClick(command.X, command.Y))
if (state == GameState.Preparing)
{
if (boardService.TryGetCell(command.X, command.Y, out var cell) && cell.IsFlagged)
{
return;
}
if (!boardService.GenerateAfterFirstClick(command.X, command.Y))
{
return;
}
}
var result = boardService.OpenCell(command.X, command.Y);
if (result.Invalid || !result.Changed)
{
return;
}
gameStateService.SetState(GameState.Playing);
if (result.HitMine)
{
gameStateService.SetState(GameState.Lost);
}
else if (result.Won)
{
gameStateService.SetState(GameState.Won);
}
else if (state == GameState.Preparing)
{
gameStateService.SetState(GameState.Playing);
}
boardEcsSyncService.SyncBoard(boardService);
boardEcsSyncService.SyncGameState(gameStateService.Current, true);
boardEcsSyncService.SyncGameState(gameStateService.Current, boardService.IsGenerated);
}
}
public sealed class ToggleFlagCommandHandler : IGameCommandHandler<ToggleFlagCommand>
{
private readonly IBoardEcsSyncService boardEcsSyncService;
private readonly IBoardService boardService;
private readonly IGameStateService gameStateService;
public ToggleFlagCommandHandler(IBoardService boardService, IBoardEcsSyncService boardEcsSyncService, IGameStateService gameStateService)
{
this.boardService = boardService;
this.boardEcsSyncService = boardEcsSyncService;
this.gameStateService = gameStateService;
}
public void Handle(ToggleFlagCommand command)
{
var state = gameStateService.Current;
if (state != GameState.Preparing && state != GameState.Playing)
{
return;
}
var result = boardService.ToggleFlag(command.X, command.Y);
if (result.Invalid || !result.Changed)
{
return;
}
boardEcsSyncService.SyncBoard(boardService);
boardEcsSyncService.SyncGameState(gameStateService.Current, boardService.IsGenerated);
}
}
@@ -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
+112 -1
View File
@@ -23,6 +23,8 @@ namespace Minesweeper.Core
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()
{
@@ -31,6 +33,7 @@ namespace Minesweeper.Core
Width = width;
Height = height;
MinesCount = minesCount;
OpenedSafeCellsCount = 0;
IsGenerated = false;
cells = new CellData[Width, Height];
@@ -55,11 +58,62 @@ namespace Minesweeper.Core
PlaceMines(safeX, safeY);
CalculateNeighborMines();
cells[safeX, safeY].IsOpened = true;
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;
@@ -190,6 +244,63 @@ namespace Minesweeper.Core
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;
@@ -8,9 +8,13 @@ namespace Minesweeper.Core
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();