[Add] Playful interaction with cells
This commit is contained in:
@@ -55,26 +55,78 @@ namespace Minesweeper.Commands
|
|||||||
|
|
||||||
public void Handle(OpenCellCommand command)
|
public void Handle(OpenCellCommand command)
|
||||||
{
|
{
|
||||||
if (gameStateService.Current != GameState.Preparing)
|
var state = gameStateService.Current;
|
||||||
|
if (state != GameState.Preparing && state != GameState.Playing)
|
||||||
{
|
{
|
||||||
return;
|
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;
|
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.SyncBoard(boardService);
|
||||||
boardEcsSyncService.SyncGameState(gameStateService.Current, true);
|
boardEcsSyncService.SyncGameState(gameStateService.Current, boardService.IsGenerated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ToggleFlagCommandHandler : IGameCommandHandler<ToggleFlagCommand>
|
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)
|
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
|
||||||
@@ -23,6 +23,8 @@ namespace Minesweeper.Core
|
|||||||
public int Height { get; private set; }
|
public int Height { get; private set; }
|
||||||
public int MinesCount { get; private set; }
|
public int MinesCount { get; private set; }
|
||||||
public bool IsGenerated { get; private set; }
|
public bool IsGenerated { get; private set; }
|
||||||
|
public int OpenedSafeCellsCount { get; private set; }
|
||||||
|
public int SafeCellsCount => Width * Height - MinesCount;
|
||||||
|
|
||||||
public void InitializeEmptyBoard()
|
public void InitializeEmptyBoard()
|
||||||
{
|
{
|
||||||
@@ -31,6 +33,7 @@ namespace Minesweeper.Core
|
|||||||
Width = width;
|
Width = width;
|
||||||
Height = height;
|
Height = height;
|
||||||
MinesCount = minesCount;
|
MinesCount = minesCount;
|
||||||
|
OpenedSafeCellsCount = 0;
|
||||||
IsGenerated = false;
|
IsGenerated = false;
|
||||||
cells = new CellData[Width, Height];
|
cells = new CellData[Width, Height];
|
||||||
|
|
||||||
@@ -55,11 +58,62 @@ namespace Minesweeper.Core
|
|||||||
PlaceMines(safeX, safeY);
|
PlaceMines(safeX, safeY);
|
||||||
CalculateNeighborMines();
|
CalculateNeighborMines();
|
||||||
|
|
||||||
cells[safeX, safeY].IsOpened = true;
|
|
||||||
IsGenerated = true;
|
IsGenerated = true;
|
||||||
return 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)
|
public bool IsInside(int x, int y)
|
||||||
{
|
{
|
||||||
return cells != null && x >= 0 && y >= 0 && x < Width && y < Height;
|
return cells != null && x >= 0 && y >= 0 && x < Width && y < Height;
|
||||||
@@ -190,6 +244,63 @@ namespace Minesweeper.Core
|
|||||||
return 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)
|
private int ToIndex(int x, int y)
|
||||||
{
|
{
|
||||||
return y * Width + x;
|
return y * Width + x;
|
||||||
|
|||||||
@@ -8,9 +8,13 @@ namespace Minesweeper.Core
|
|||||||
int Height { get; }
|
int Height { get; }
|
||||||
int MinesCount { get; }
|
int MinesCount { get; }
|
||||||
bool IsGenerated { get; }
|
bool IsGenerated { get; }
|
||||||
|
int OpenedSafeCellsCount { get; }
|
||||||
|
int SafeCellsCount { get; }
|
||||||
|
|
||||||
void InitializeEmptyBoard();
|
void InitializeEmptyBoard();
|
||||||
bool GenerateAfterFirstClick(int safeX, int safeY);
|
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 IsInside(int x, int y);
|
||||||
bool TryGetCell(int x, int y, out BoardCellData cell);
|
bool TryGetCell(int x, int y, out BoardCellData cell);
|
||||||
IReadOnlyList<BoardCellData> GetCells();
|
IReadOnlyList<BoardCellData> GetCells();
|
||||||
|
|||||||
Reference in New Issue
Block a user