[Fix] Rename Scripts Folder
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7c135ae68601e5439b15457e8027a3f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
|
||||
namespace Minesweeper.Commands
|
||||
{
|
||||
public sealed class GameCommandDispatcher : IGameCommandDispatcher
|
||||
{
|
||||
private readonly SelectFieldCommandHandler selectFieldHandler;
|
||||
private readonly StartGameCommandHandler startGameHandler;
|
||||
private readonly OpenCellCommandHandler openCellHandler;
|
||||
private readonly ToggleFlagCommandHandler toggleFlagHandler;
|
||||
private readonly RestartCommandHandler restartHandler;
|
||||
private readonly PauseCommandHandler pauseHandler;
|
||||
private readonly ResumeCommandHandler resumeHandler;
|
||||
private readonly GoToMenuCommandHandler goToMenuHandler;
|
||||
|
||||
public GameCommandDispatcher(
|
||||
SelectFieldCommandHandler selectFieldHandler,
|
||||
StartGameCommandHandler startGameHandler,
|
||||
OpenCellCommandHandler openCellHandler,
|
||||
ToggleFlagCommandHandler toggleFlagHandler,
|
||||
RestartCommandHandler restartHandler,
|
||||
PauseCommandHandler pauseHandler,
|
||||
ResumeCommandHandler resumeHandler,
|
||||
GoToMenuCommandHandler goToMenuHandler)
|
||||
{
|
||||
this.selectFieldHandler = selectFieldHandler;
|
||||
this.startGameHandler = startGameHandler;
|
||||
this.openCellHandler = openCellHandler;
|
||||
this.toggleFlagHandler = toggleFlagHandler;
|
||||
this.restartHandler = restartHandler;
|
||||
this.pauseHandler = pauseHandler;
|
||||
this.resumeHandler = resumeHandler;
|
||||
this.goToMenuHandler = goToMenuHandler;
|
||||
}
|
||||
|
||||
public void Dispatch<TCommand>(TCommand command) where TCommand : IGameCommand
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case SelectFieldCommand selectFieldCommand:
|
||||
selectFieldHandler.Handle(selectFieldCommand);
|
||||
return;
|
||||
case StartGameCommand startGameCommand:
|
||||
startGameHandler.Handle(startGameCommand);
|
||||
return;
|
||||
case OpenCellCommand openCellCommand:
|
||||
openCellHandler.Handle(openCellCommand);
|
||||
return;
|
||||
case ToggleFlagCommand toggleFlagCommand:
|
||||
toggleFlagHandler.Handle(toggleFlagCommand);
|
||||
return;
|
||||
case RestartCommand restartCommand:
|
||||
restartHandler.Handle(restartCommand);
|
||||
return;
|
||||
case PauseCommand pauseCommand:
|
||||
pauseHandler.Handle(pauseCommand);
|
||||
return;
|
||||
case ResumeCommand resumeCommand:
|
||||
resumeHandler.Handle(resumeCommand);
|
||||
return;
|
||||
case GoToMenuCommand goToMenuCommand:
|
||||
goToMenuHandler.Handle(goToMenuCommand);
|
||||
return;
|
||||
default:
|
||||
throw new InvalidOperationException($"No handler registered for command {typeof(TCommand).Name}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6383c559964ec3545a7cd911b90586ce
|
||||
@@ -0,0 +1,246 @@
|
||||
using Minesweeper.Core;
|
||||
using Minesweeper.ECS;
|
||||
|
||||
namespace Minesweeper.Commands
|
||||
{
|
||||
public sealed class SelectFieldCommandHandler : IGameCommandHandler<SelectFieldCommand>
|
||||
{
|
||||
private readonly IGameStateService gameStateService;
|
||||
|
||||
public SelectFieldCommandHandler(IGameStateService gameStateService)
|
||||
{
|
||||
this.gameStateService = gameStateService;
|
||||
}
|
||||
|
||||
public void Handle(SelectFieldCommand command)
|
||||
{
|
||||
gameStateService.SetState(GameState.FieldSelection);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StartGameCommandHandler : IGameCommandHandler<StartGameCommand>
|
||||
{
|
||||
private readonly IBoardEcsSyncService boardEcsSyncService;
|
||||
private readonly IBoardService boardService;
|
||||
private readonly IGamePauseService pauseService;
|
||||
private readonly IGameStateService gameStateService;
|
||||
private readonly IGameTimerService timerService;
|
||||
|
||||
public StartGameCommandHandler(IBoardService boardService, IBoardEcsSyncService boardEcsSyncService, IGamePauseService pauseService, IGameStateService gameStateService, IGameTimerService timerService)
|
||||
{
|
||||
this.boardService = boardService;
|
||||
this.boardEcsSyncService = boardEcsSyncService;
|
||||
this.pauseService = pauseService;
|
||||
this.gameStateService = gameStateService;
|
||||
this.timerService = timerService;
|
||||
}
|
||||
|
||||
public void Handle(StartGameCommand command)
|
||||
{
|
||||
pauseService.Resume();
|
||||
timerService.Reset();
|
||||
boardService.InitializeEmptyBoard();
|
||||
gameStateService.SetState(GameState.Preparing);
|
||||
boardEcsSyncService.SyncBoard(boardService);
|
||||
boardEcsSyncService.SyncGameState(gameStateService.Current, false);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class OpenCellCommandHandler : IGameCommandHandler<OpenCellCommand>
|
||||
{
|
||||
private readonly IBoardEcsSyncService boardEcsSyncService;
|
||||
private readonly IBoardService boardService;
|
||||
private readonly IGameStateService gameStateService;
|
||||
private readonly IGamePauseService pauseService;
|
||||
|
||||
public OpenCellCommandHandler(IBoardService boardService, IBoardEcsSyncService boardEcsSyncService, IGameStateService gameStateService, IGamePauseService pauseService)
|
||||
{
|
||||
this.boardService = boardService;
|
||||
this.boardEcsSyncService = boardEcsSyncService;
|
||||
this.gameStateService = gameStateService;
|
||||
this.pauseService = pauseService;
|
||||
}
|
||||
|
||||
public void Handle(OpenCellCommand command)
|
||||
{
|
||||
if (pauseService.IsPaused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var state = gameStateService.Current;
|
||||
if (state != GameState.Preparing && state != GameState.Playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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, boardService.IsGenerated);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ToggleFlagCommandHandler : IGameCommandHandler<ToggleFlagCommand>
|
||||
{
|
||||
private readonly IBoardEcsSyncService boardEcsSyncService;
|
||||
private readonly IBoardService boardService;
|
||||
private readonly IGameStateService gameStateService;
|
||||
private readonly IGamePauseService pauseService;
|
||||
|
||||
public ToggleFlagCommandHandler(IBoardService boardService, IBoardEcsSyncService boardEcsSyncService, IGameStateService gameStateService, IGamePauseService pauseService)
|
||||
{
|
||||
this.boardService = boardService;
|
||||
this.boardEcsSyncService = boardEcsSyncService;
|
||||
this.gameStateService = gameStateService;
|
||||
this.pauseService = pauseService;
|
||||
}
|
||||
|
||||
public void Handle(ToggleFlagCommand command)
|
||||
{
|
||||
if (pauseService.IsPaused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RestartCommandHandler : IGameCommandHandler<RestartCommand>
|
||||
{
|
||||
private readonly IBoardEcsSyncService boardEcsSyncService;
|
||||
private readonly IBoardService boardService;
|
||||
private readonly IGamePauseService pauseService;
|
||||
private readonly IGameStateService gameStateService;
|
||||
private readonly IGameTimerService timerService;
|
||||
|
||||
public RestartCommandHandler(IBoardService boardService, IBoardEcsSyncService boardEcsSyncService, IGamePauseService pauseService, IGameStateService gameStateService, IGameTimerService timerService)
|
||||
{
|
||||
this.boardService = boardService;
|
||||
this.boardEcsSyncService = boardEcsSyncService;
|
||||
this.pauseService = pauseService;
|
||||
this.gameStateService = gameStateService;
|
||||
this.timerService = timerService;
|
||||
}
|
||||
|
||||
public void Handle(RestartCommand command)
|
||||
{
|
||||
var shouldNotifyReset = gameStateService.Current == GameState.Preparing;
|
||||
pauseService.Resume();
|
||||
timerService.Reset();
|
||||
boardService.InitializeEmptyBoard();
|
||||
gameStateService.SetState(GameState.Preparing);
|
||||
boardEcsSyncService.SyncBoard(boardService);
|
||||
boardEcsSyncService.SyncGameState(gameStateService.Current, false);
|
||||
|
||||
if (shouldNotifyReset)
|
||||
{
|
||||
gameStateService.NotifyCurrentStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PauseCommandHandler : IGameCommandHandler<PauseCommand>
|
||||
{
|
||||
private readonly IGamePauseService pauseService;
|
||||
private readonly IGameStateService gameStateService;
|
||||
|
||||
public PauseCommandHandler(IGamePauseService pauseService, IGameStateService gameStateService)
|
||||
{
|
||||
this.pauseService = pauseService;
|
||||
this.gameStateService = gameStateService;
|
||||
}
|
||||
|
||||
public void Handle(PauseCommand command)
|
||||
{
|
||||
if (gameStateService.Current == GameState.Playing)
|
||||
{
|
||||
pauseService.Pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ResumeCommandHandler : IGameCommandHandler<ResumeCommand>
|
||||
{
|
||||
private readonly IGamePauseService pauseService;
|
||||
|
||||
public ResumeCommandHandler(IGamePauseService pauseService)
|
||||
{
|
||||
this.pauseService = pauseService;
|
||||
}
|
||||
|
||||
public void Handle(ResumeCommand command)
|
||||
{
|
||||
pauseService.Resume();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GoToMenuCommandHandler : IGameCommandHandler<GoToMenuCommand>
|
||||
{
|
||||
private readonly IBoardEcsSyncService boardEcsSyncService;
|
||||
private readonly IGamePauseService pauseService;
|
||||
private readonly IGameStateService gameStateService;
|
||||
private readonly IGameTimerService timerService;
|
||||
|
||||
public GoToMenuCommandHandler(IBoardEcsSyncService boardEcsSyncService, IGamePauseService pauseService, IGameStateService gameStateService, IGameTimerService timerService)
|
||||
{
|
||||
this.boardEcsSyncService = boardEcsSyncService;
|
||||
this.pauseService = pauseService;
|
||||
this.gameStateService = gameStateService;
|
||||
this.timerService = timerService;
|
||||
}
|
||||
|
||||
public void Handle(GoToMenuCommand command)
|
||||
{
|
||||
pauseService.Resume();
|
||||
timerService.Reset();
|
||||
gameStateService.SetState(GameState.FieldSelection);
|
||||
boardEcsSyncService.ClearBoard();
|
||||
boardEcsSyncService.SyncGameState(gameStateService.Current, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31bfefc0901594043aae51cabba89234
|
||||
@@ -0,0 +1,60 @@
|
||||
namespace Minesweeper.Commands
|
||||
{
|
||||
public readonly struct SelectFieldCommand : IGameCommand
|
||||
{
|
||||
public SelectFieldCommand(int width, int height, int minesCount)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
MinesCount = minesCount;
|
||||
}
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
public int MinesCount { get; }
|
||||
}
|
||||
|
||||
public readonly struct StartGameCommand : IGameCommand
|
||||
{
|
||||
}
|
||||
|
||||
public readonly struct OpenCellCommand : IGameCommand
|
||||
{
|
||||
public OpenCellCommand(int x, int y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public int X { get; }
|
||||
public int Y { get; }
|
||||
}
|
||||
|
||||
public readonly struct ToggleFlagCommand : IGameCommand
|
||||
{
|
||||
public ToggleFlagCommand(int x, int y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public int X { get; }
|
||||
public int Y { get; }
|
||||
}
|
||||
|
||||
public readonly struct RestartCommand : IGameCommand
|
||||
{
|
||||
}
|
||||
|
||||
public readonly struct PauseCommand : IGameCommand
|
||||
{
|
||||
}
|
||||
|
||||
public readonly struct ResumeCommand : IGameCommand
|
||||
{
|
||||
}
|
||||
|
||||
public readonly struct GoToMenuCommand : IGameCommand
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9bd93535958ca574999f8ec6de84baa3
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Minesweeper.Commands
|
||||
{
|
||||
public interface IGameCommand
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50cd27fcb4d423e43b2a56cc23badbfb
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Minesweeper.Commands
|
||||
{
|
||||
public interface IGameCommandDispatcher
|
||||
{
|
||||
void Dispatch<TCommand>(TCommand command) where TCommand : IGameCommand;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1c291311029640428d2ea8820fdfeaa
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Minesweeper.Commands
|
||||
{
|
||||
public interface IGameCommandHandler<in TCommand> where TCommand : IGameCommand
|
||||
{
|
||||
void Handle(TCommand command);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7b5f5892af9c184ba720b2b3f352b32
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a904ba5489d53724692eb73dd2a68f2e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minesweeper.Config
|
||||
{
|
||||
[CreateAssetMenu(fileName = "MinesweeperGameConfig", menuName = "Minesweeper/Game Config")]
|
||||
public sealed class MinesweeperGameConfig : ScriptableObject
|
||||
{
|
||||
[field: SerializeField, Min(1)] public int MinSizeX { get; private set; } = 2;
|
||||
[field: SerializeField, Min(1)] public int MaxSizeX { get; private set; } = 50;
|
||||
[field: SerializeField, Min(1)] public int MinSizeY { get; private set; } = 2;
|
||||
[field: SerializeField, Min(1)] public int MaxSizeY { get; private set; } = 50;
|
||||
|
||||
public bool IsValid => MinSizeX > 0 && MinSizeY > 0 && MaxSizeX >= MinSizeX && MaxSizeY >= MinSizeY && MaxSizeX * MaxSizeY > 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4e8d5c36f36bb443b640a85df3e7077
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minesweeper.Config
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class MinesweeperScreenCatalog
|
||||
{
|
||||
[field: SerializeField] public GameObject MainMenuPanelPrefab { get; private set; }
|
||||
[field: SerializeField] public GameObject BoardGridPrefab { get; private set; }
|
||||
[field: SerializeField] public GameObject PausePanelPrefab { get; private set; }
|
||||
[field: SerializeField] public GameObject ResultPanelPrefab { get; private set; }
|
||||
|
||||
public bool IsValid => MainMenuPanelPrefab != null && BoardGridPrefab != null && PausePanelPrefab != null && ResultPanelPrefab != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d0009ea7e70ee548a09cd95d482ec83
|
||||
@@ -0,0 +1,76 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minesweeper.Config
|
||||
{
|
||||
[CreateAssetMenu(fileName = "MinesweeperUiConfig", menuName = "Minesweeper/UI Config")]
|
||||
public sealed class MinesweeperUiConfig : ScriptableObject
|
||||
{
|
||||
public const float DefaultReferenceCellSize = 100f;
|
||||
public const float DefaultBoardPaddingRatio = 0.15f;
|
||||
public const float DefaultGridSpacingRatio = 0.02f;
|
||||
public const float DefaultMinimumCellSize = 8f;
|
||||
|
||||
[field: SerializeField] public Sprite SmileSprite { get; private set; }
|
||||
[field: SerializeField] public Sprite SurprisedSprite { get; private set; }
|
||||
[field: SerializeField] public Sprite CoolSprite { get; private set; }
|
||||
[field: SerializeField] public Sprite DeadSprite { get; private set; }
|
||||
[field: SerializeField] public GameObject CellButtonPrefab { get; private set; }
|
||||
[field: SerializeField] public Sprite MineSprite { get; private set; }
|
||||
[field: SerializeField] public Sprite FlagSprite { get; private set; }
|
||||
[field: SerializeField] public Color ClosedCellColor { get; private set; } = Color.gray;
|
||||
[field: SerializeField] public Color OpenedCellColor { get; private set; } = Color.white;
|
||||
[SerializeField] private Color defaultTextColor = Color.black;
|
||||
[SerializeField] private Color oneMineTextColor = Color.blue;
|
||||
[SerializeField] private Color twoMineTextColor = Color.green;
|
||||
[SerializeField] private Color threeMineTextColor = Color.red;
|
||||
[SerializeField] private Color fourMineTextColor = new Color(0f, 0f, 0.5f, 1f);
|
||||
[SerializeField] private Color fiveMineTextColor = new Color(0.5f, 0f, 0f, 1f);
|
||||
[SerializeField] private Color sixMineTextColor = Color.cyan;
|
||||
[SerializeField] private Color sevenMineTextColor = Color.black;
|
||||
[SerializeField] private Color eightMineTextColor = Color.gray;
|
||||
[field: SerializeField, Min(DefaultMinimumCellSize)] public float ReferenceCellSize { get; private set; } = DefaultReferenceCellSize;
|
||||
[field: SerializeField, Min(0f)] public float BoardPaddingRatio { get; private set; } = DefaultBoardPaddingRatio;
|
||||
[field: SerializeField, Min(0f)] public float GridSpacingRatio { get; private set; } = DefaultGridSpacingRatio;
|
||||
[field: SerializeField, Min(DefaultMinimumCellSize)] public float MinimumCellSize { get; private set; } = DefaultMinimumCellSize;
|
||||
|
||||
public Sprite GetSmileSprite(SmileFaceState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case SmileFaceState.Surprised:
|
||||
return SurprisedSprite != null ? SurprisedSprite : SmileSprite;
|
||||
case SmileFaceState.Cool:
|
||||
return CoolSprite != null ? CoolSprite : SmileSprite;
|
||||
case SmileFaceState.Dead:
|
||||
return DeadSprite != null ? DeadSprite : SmileSprite;
|
||||
default:
|
||||
return SmileSprite;
|
||||
}
|
||||
}
|
||||
|
||||
public Color GetNumberTextColor(int neighborMines)
|
||||
{
|
||||
switch (neighborMines)
|
||||
{
|
||||
case 1:
|
||||
return oneMineTextColor;
|
||||
case 2:
|
||||
return twoMineTextColor;
|
||||
case 3:
|
||||
return threeMineTextColor;
|
||||
case 4:
|
||||
return fourMineTextColor;
|
||||
case 5:
|
||||
return fiveMineTextColor;
|
||||
case 6:
|
||||
return sixMineTextColor;
|
||||
case 7:
|
||||
return sevenMineTextColor;
|
||||
case 8:
|
||||
return eightMineTextColor;
|
||||
default:
|
||||
return defaultTextColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3b0d5a2a71d45ad9f4ac4f77158c101
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Minesweeper.Config
|
||||
{
|
||||
public enum SmileFaceState
|
||||
{
|
||||
Smile = 0,
|
||||
Surprised = 1,
|
||||
Cool = 2,
|
||||
Dead = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f984c51b7db249ac9b9887b2b2bc75ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 391f2b5ec87a6634694fe2c31faee82e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11dec36e720c3e745b9dc11d588b5794
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Minesweeper.Core
|
||||
{
|
||||
public enum GameState
|
||||
{
|
||||
FieldSelection,
|
||||
Preparing,
|
||||
Playing,
|
||||
Lost,
|
||||
Won
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c06ebf54d6bacdf4888fabbf29bea1cd
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebd50c14109e96541aa49542a07986aa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,120 @@
|
||||
using Minesweeper.Core;
|
||||
using Minesweeper.ECS.Components;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
|
||||
namespace Minesweeper.ECS
|
||||
{
|
||||
public sealed class BoardEcsSyncService : IBoardEcsSyncService
|
||||
{
|
||||
public void ClearBoard()
|
||||
{
|
||||
if (!TryGetEntityManager(out var entityManager))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClearCells(entityManager);
|
||||
}
|
||||
|
||||
public void SyncBoard(IBoardService boardService)
|
||||
{
|
||||
if (!TryGetEntityManager(out var entityManager))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClearCells(entityManager);
|
||||
|
||||
var boardEntity = GetOrCreateSingleton<BoardConfigComponent>(entityManager);
|
||||
entityManager.SetComponentData(boardEntity, new BoardConfigComponent
|
||||
{
|
||||
Width = boardService.Width,
|
||||
Height = boardService.Height,
|
||||
MinesCount = boardService.MinesCount
|
||||
});
|
||||
|
||||
var archetype = entityManager.CreateArchetype(typeof(CellComponent));
|
||||
var cells = boardService.GetCells();
|
||||
for (var i = 0; i < cells.Count; i++)
|
||||
{
|
||||
var cell = cells[i];
|
||||
var entity = entityManager.CreateEntity(archetype);
|
||||
entityManager.SetComponentData(entity, new CellComponent
|
||||
{
|
||||
X = cell.X,
|
||||
Y = cell.Y,
|
||||
IsMine = ToByte(cell.IsMine),
|
||||
IsOpened = ToByte(cell.IsOpened),
|
||||
IsFlagged = ToByte(cell.IsFlagged),
|
||||
NeighborMines = cell.NeighborMines
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void SyncGameState(GameState state, bool hasFirstClick)
|
||||
{
|
||||
if (!TryGetEntityManager(out var entityManager))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var stateEntity = GetOrCreateSingleton<GameStateComponent>(entityManager);
|
||||
entityManager.SetComponentData(stateEntity, new GameStateComponent
|
||||
{
|
||||
State = state,
|
||||
HasFirstClick = ToByte(hasFirstClick)
|
||||
});
|
||||
}
|
||||
|
||||
private static bool TryGetEntityManager(out EntityManager entityManager)
|
||||
{
|
||||
var world = World.DefaultGameObjectInjectionWorld;
|
||||
if (world == null || !world.IsCreated)
|
||||
{
|
||||
entityManager = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
entityManager = world.EntityManager;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ClearCells(EntityManager entityManager)
|
||||
{
|
||||
var query = entityManager.CreateEntityQuery(typeof(CellComponent));
|
||||
entityManager.DestroyEntity(query);
|
||||
query.Dispose();
|
||||
}
|
||||
|
||||
private static Entity GetOrCreateSingleton<T>(EntityManager entityManager) where T : unmanaged, IComponentData
|
||||
{
|
||||
var query = entityManager.CreateEntityQuery(typeof(T));
|
||||
Entity entity;
|
||||
|
||||
if (query.IsEmptyIgnoreFilter)
|
||||
{
|
||||
entity = entityManager.CreateEntity(typeof(T));
|
||||
}
|
||||
else
|
||||
{
|
||||
var entities = query.ToEntityArray(Allocator.Temp);
|
||||
entity = entities[0];
|
||||
for (var i = 1; i < entities.Length; i++)
|
||||
{
|
||||
entityManager.DestroyEntity(entities[i]);
|
||||
}
|
||||
|
||||
entities.Dispose();
|
||||
}
|
||||
|
||||
query.Dispose();
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static byte ToByte(bool value)
|
||||
{
|
||||
return value ? (byte)1 : (byte)0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ad76f74587f648429ed4e14a39e4d13
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ebb3bd7d4baf544fadefb7718c935d2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace Minesweeper.ECS.Components
|
||||
{
|
||||
public struct BoardConfigComponent : IComponentData
|
||||
{
|
||||
public int Width;
|
||||
public int Height;
|
||||
public int MinesCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0024f7b9432b3740bdb6ae9ab132529
|
||||
@@ -0,0 +1,14 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace Minesweeper.ECS.Components
|
||||
{
|
||||
public struct CellComponent : IComponentData
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
public byte IsMine;
|
||||
public byte IsOpened;
|
||||
public byte IsFlagged;
|
||||
public int NeighborMines;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50ce9bb3f8ca1c142a28adb88899afec
|
||||
@@ -0,0 +1,11 @@
|
||||
using Minesweeper.Core;
|
||||
using Unity.Entities;
|
||||
|
||||
namespace Minesweeper.ECS.Components
|
||||
{
|
||||
public struct GameStateComponent : IComponentData
|
||||
{
|
||||
public GameState State;
|
||||
public byte HasFirstClick;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a89195585a555b54e909b4f8797f20ed
|
||||
@@ -0,0 +1,11 @@
|
||||
using Minesweeper.Core;
|
||||
|
||||
namespace Minesweeper.ECS
|
||||
{
|
||||
public interface IBoardEcsSyncService
|
||||
{
|
||||
void ClearBoard();
|
||||
void SyncBoard(IBoardService boardService);
|
||||
void SyncGameState(GameState state, bool hasFirstClick);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 779db0eb469cc2449bce502b6e46231d
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 210d08f5d5948674fa0df51ed6a785b0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbb80c6f3d55e8d418e4f3369b2e1623
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using Minesweeper.Presentation.Presenters;
|
||||
using VContainer.Unity;
|
||||
|
||||
namespace Minesweeper.Infrastructure
|
||||
{
|
||||
public sealed class MinesweeperEntryPoint : IStartable, IDisposable
|
||||
{
|
||||
private readonly MainMenuPresenter mainMenuPresenter;
|
||||
private readonly TopPanelPresenter topPanelPresenter;
|
||||
private readonly GamePresenter gamePresenter;
|
||||
|
||||
public MinesweeperEntryPoint(MainMenuPresenter mainMenuPresenter, TopPanelPresenter topPanelPresenter, GamePresenter gamePresenter)
|
||||
{
|
||||
this.mainMenuPresenter = mainMenuPresenter;
|
||||
this.topPanelPresenter = topPanelPresenter;
|
||||
this.gamePresenter = gamePresenter;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
topPanelPresenter.Initialize();
|
||||
mainMenuPresenter.Initialize();
|
||||
gamePresenter.Initialize();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
gamePresenter.Dispose();
|
||||
mainMenuPresenter.Dispose();
|
||||
topPanelPresenter.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5eae713b4d801be4996a70d4b630eeee
|
||||
@@ -0,0 +1,158 @@
|
||||
using Minesweeper.Commands;
|
||||
using Minesweeper.Config;
|
||||
using Minesweeper.Core;
|
||||
using Minesweeper.ECS;
|
||||
using Minesweeper.Presentation.Adapters;
|
||||
using Minesweeper.Presentation.Factories;
|
||||
using Minesweeper.Presentation.Presenters;
|
||||
using Minesweeper.Presentation.ReadModels;
|
||||
using Minesweeper.Presentation.Views;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
using VContainer.Unity;
|
||||
|
||||
namespace Minesweeper.Infrastructure
|
||||
{
|
||||
public sealed class MinesweeperLifetimeScope : LifetimeScope
|
||||
{
|
||||
[SerializeField] private MinesweeperGameConfig gameConfig;
|
||||
[SerializeField] private MinesweeperUiConfig uiConfig;
|
||||
[SerializeField] private MinesweeperScreenCatalog screenCatalog = new MinesweeperScreenCatalog();
|
||||
[SerializeField] private Transform contentRoot;
|
||||
[SerializeField] private TopPanelView topPanelView;
|
||||
[SerializeField] private MainMenuView mainMenuView;
|
||||
[SerializeField] private BoardView boardView;
|
||||
[SerializeField] private PauseView pauseView;
|
||||
[SerializeField] private ResultView resultView;
|
||||
|
||||
protected override void Configure(IContainerBuilder builder)
|
||||
{
|
||||
builder.RegisterInstance(GetConfig());
|
||||
var resolvedUiConfig = GetUiConfig();
|
||||
builder.RegisterInstance(resolvedUiConfig);
|
||||
builder.Register<PlayerPrefsGameSettingsStorage>(Lifetime.Singleton).As<IGameSettingsStorage>();
|
||||
builder.Register<GameSettingsService>(Lifetime.Singleton).As<IGameSettingsService>();
|
||||
builder.Register<GameStateService>(Lifetime.Singleton).As<IGameStateService>();
|
||||
builder.Register<BoardService>(Lifetime.Singleton).As<IBoardService>();
|
||||
builder.Register<GamePauseService>(Lifetime.Singleton).As<IGamePauseService>();
|
||||
builder.Register<GameTimerService>(Lifetime.Singleton).As<IGameTimerService>().As<ITickable>();
|
||||
builder.Register<BoardEcsSyncService>(Lifetime.Singleton).As<IBoardEcsSyncService>();
|
||||
builder.Register<GameReadModel>(Lifetime.Singleton).As<IGameReadModel>();
|
||||
builder.Register<GameStateViewAdapter>(Lifetime.Singleton).As<IGameStateViewAdapter>();
|
||||
builder.Register<CellViewFactory>(Lifetime.Singleton).As<ICellViewFactory>();
|
||||
|
||||
var screenRefs = SpawnScreens();
|
||||
if (screenRefs.MainMenuView != null)
|
||||
{
|
||||
mainMenuView = screenRefs.MainMenuView;
|
||||
}
|
||||
|
||||
if (screenRefs.BoardView != null)
|
||||
{
|
||||
boardView = screenRefs.BoardView;
|
||||
}
|
||||
|
||||
if (screenRefs.PauseView != null)
|
||||
{
|
||||
pauseView = screenRefs.PauseView;
|
||||
}
|
||||
|
||||
if (screenRefs.ResultView != null)
|
||||
{
|
||||
resultView = screenRefs.ResultView;
|
||||
}
|
||||
|
||||
if (topPanelView != null)
|
||||
{
|
||||
topPanelView.BindConfig(resolvedUiConfig);
|
||||
builder.RegisterComponent(topPanelView).As<ITopPanelView>();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Register<NullTopPanelView>(Lifetime.Singleton).As<ITopPanelView>();
|
||||
}
|
||||
|
||||
if (mainMenuView != null)
|
||||
{
|
||||
builder.RegisterComponent(mainMenuView).As<IMainMenuView>();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Register<NullMainMenuView>(Lifetime.Singleton).As<IMainMenuView>();
|
||||
}
|
||||
|
||||
if (boardView != null)
|
||||
{
|
||||
boardView.BindConfig(resolvedUiConfig);
|
||||
builder.RegisterComponent(boardView).As<IBoardView>();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Register<NullBoardView>(Lifetime.Singleton).As<IBoardView>();
|
||||
}
|
||||
|
||||
if (pauseView != null)
|
||||
{
|
||||
builder.RegisterComponent(pauseView).As<IPauseView>();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Register<NullPauseView>(Lifetime.Singleton).As<IPauseView>();
|
||||
}
|
||||
|
||||
if (resultView != null)
|
||||
{
|
||||
builder.RegisterComponent(resultView).As<IResultView>();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Register<NullResultView>(Lifetime.Singleton).As<IResultView>();
|
||||
}
|
||||
|
||||
builder.Register<SelectFieldCommandHandler>(Lifetime.Singleton);
|
||||
builder.Register<StartGameCommandHandler>(Lifetime.Singleton);
|
||||
builder.Register<OpenCellCommandHandler>(Lifetime.Singleton);
|
||||
builder.Register<ToggleFlagCommandHandler>(Lifetime.Singleton);
|
||||
builder.Register<RestartCommandHandler>(Lifetime.Singleton);
|
||||
builder.Register<PauseCommandHandler>(Lifetime.Singleton);
|
||||
builder.Register<ResumeCommandHandler>(Lifetime.Singleton);
|
||||
builder.Register<GoToMenuCommandHandler>(Lifetime.Singleton);
|
||||
builder.Register<GameCommandDispatcher>(Lifetime.Singleton).As<IGameCommandDispatcher>();
|
||||
|
||||
builder.Register<MainMenuPresenter>(Lifetime.Singleton);
|
||||
builder.Register<TopPanelPresenter>(Lifetime.Singleton);
|
||||
builder.Register<GamePresenter>(Lifetime.Singleton);
|
||||
builder.RegisterEntryPoint<MinesweeperEntryPoint>();
|
||||
}
|
||||
|
||||
private MinesweeperGameConfig GetConfig()
|
||||
{
|
||||
if (gameConfig != null)
|
||||
{
|
||||
return gameConfig;
|
||||
}
|
||||
|
||||
return ScriptableObject.CreateInstance<MinesweeperGameConfig>();
|
||||
}
|
||||
|
||||
private MinesweeperUiConfig GetUiConfig()
|
||||
{
|
||||
if (uiConfig != null)
|
||||
{
|
||||
return uiConfig;
|
||||
}
|
||||
|
||||
return ScriptableObject.CreateInstance<MinesweeperUiConfig>();
|
||||
}
|
||||
|
||||
private MinesweeperScreenRefs SpawnScreens()
|
||||
{
|
||||
if (contentRoot == null || screenCatalog == null || !screenCatalog.IsValid)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new MinesweeperScreenBootstrapper().Spawn(contentRoot, screenCatalog);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4f9b0c2ad803d84382fbf03ba3096fa
|
||||
@@ -0,0 +1,32 @@
|
||||
using Minesweeper.Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minesweeper.Infrastructure
|
||||
{
|
||||
public sealed class PlayerPrefsGameSettingsStorage : IGameSettingsStorage
|
||||
{
|
||||
private const string SizeXKey = "Minesweeper.Settings.SizeX";
|
||||
private const string SizeYKey = "Minesweeper.Settings.SizeY";
|
||||
private const string MinesCountKey = "Minesweeper.Settings.MinesCount";
|
||||
|
||||
public bool TryLoad(out GameSettingsValue value)
|
||||
{
|
||||
if (!PlayerPrefs.HasKey(SizeXKey) || !PlayerPrefs.HasKey(SizeYKey) || !PlayerPrefs.HasKey(MinesCountKey))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = new GameSettingsValue(PlayerPrefs.GetInt(SizeXKey), PlayerPrefs.GetInt(SizeYKey), PlayerPrefs.GetInt(MinesCountKey));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Save(GameSettingsValue value)
|
||||
{
|
||||
PlayerPrefs.SetInt(SizeXKey, value.SizeX);
|
||||
PlayerPrefs.SetInt(SizeYKey, value.SizeY);
|
||||
PlayerPrefs.SetInt(MinesCountKey, value.MinesCount);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a001fc2e924d95643a075190b6cadda1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1435ba5f7e9b514cb863ee06385cb77
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 648cba2aa826df04aa6de2236d532ff7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using Minesweeper.Core;
|
||||
|
||||
namespace Minesweeper.Presentation.Adapters
|
||||
{
|
||||
public sealed class GameStateViewAdapter : IGameStateViewAdapter
|
||||
{
|
||||
public string GetDisplayName(GameState state)
|
||||
{
|
||||
return state.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a10e635e4da16d24db457ead4d139896
|
||||
@@ -0,0 +1,9 @@
|
||||
using Minesweeper.Core;
|
||||
|
||||
namespace Minesweeper.Presentation.Adapters
|
||||
{
|
||||
public interface IGameStateViewAdapter
|
||||
{
|
||||
string GetDisplayName(GameState state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 058bab5bc0f87f64dbbf668e5e390216
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec6192ca6506ac64c8aa089466c86db5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using Minesweeper.Config;
|
||||
using Minesweeper.Core;
|
||||
using Minesweeper.Presentation.Views;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minesweeper.Presentation.Factories
|
||||
{
|
||||
public sealed class CellViewFactory : ICellViewFactory
|
||||
{
|
||||
private readonly MinesweeperUiConfig uiConfig;
|
||||
|
||||
public CellViewFactory(MinesweeperUiConfig uiConfig)
|
||||
{
|
||||
this.uiConfig = uiConfig;
|
||||
}
|
||||
|
||||
public CellView CreateCell(BoardCellData cell, Transform parent)
|
||||
{
|
||||
var prefab = uiConfig.CellButtonPrefab;
|
||||
if (prefab == null)
|
||||
{
|
||||
Debug.LogError("CellViewFactory failed: CellButtonPrefab is not assigned.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var go = Object.Instantiate(prefab, parent);
|
||||
go.name = BuildCellName(cell.X, cell.Y, cell.DisplayValue);
|
||||
|
||||
var view = go.GetComponent<CellView>();
|
||||
if (view == null)
|
||||
{
|
||||
Debug.LogError($"CellViewFactory failed: '{prefab.name}' is missing CellView.");
|
||||
Object.Destroy(go);
|
||||
return null;
|
||||
}
|
||||
|
||||
view.Initialize(cell.X, cell.Y);
|
||||
return view;
|
||||
}
|
||||
|
||||
public string BuildCellName(int x, int y, int value, bool isMine)
|
||||
{
|
||||
return $"bt_{x}_{y}_{(isMine ? "M" : value.ToString())}";
|
||||
}
|
||||
|
||||
public string BuildCellName(int x, int y, string displayValue)
|
||||
{
|
||||
return $"bt_{x}_{y}_{displayValue}";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 681a16a970ddf3546bb4f99d93a184ca
|
||||
@@ -0,0 +1,13 @@
|
||||
using Minesweeper.Core;
|
||||
using Minesweeper.Presentation.Views;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minesweeper.Presentation.Factories
|
||||
{
|
||||
public interface ICellViewFactory
|
||||
{
|
||||
CellView CreateCell(BoardCellData cell, Transform parent);
|
||||
string BuildCellName(int x, int y, int value, bool isMine);
|
||||
string BuildCellName(int x, int y, string displayValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34c31a0f01c730440a2bcdac0f775dd8
|
||||
@@ -0,0 +1,102 @@
|
||||
using Minesweeper.Config;
|
||||
using Minesweeper.Presentation.Views;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minesweeper.Presentation.Factories
|
||||
{
|
||||
public sealed class MinesweeperScreenBootstrapper
|
||||
{
|
||||
private const string MainMenuPanelName = "MainMenuPanel";
|
||||
private const string BoardGridName = "BoardGrid";
|
||||
private const string PausePanelName = "PausePanel";
|
||||
private const string ResultPanelName = "ResultPanel";
|
||||
|
||||
public MinesweeperScreenRefs Spawn(Transform contentRoot, MinesweeperScreenCatalog catalog)
|
||||
{
|
||||
if (contentRoot == null)
|
||||
{
|
||||
Debug.LogError("Minesweeper screen bootstrap failed: Content root is not assigned.");
|
||||
return default;
|
||||
}
|
||||
|
||||
if (catalog == null || !catalog.IsValid)
|
||||
{
|
||||
Debug.LogError("Minesweeper screen bootstrap failed: screen catalog prefab references are incomplete.");
|
||||
return default;
|
||||
}
|
||||
|
||||
ClearContent(contentRoot);
|
||||
|
||||
var mainMenu = SpawnScreen(catalog.MainMenuPanelPrefab, contentRoot, MainMenuPanelName, 0);
|
||||
var board = SpawnScreen(catalog.BoardGridPrefab, contentRoot, BoardGridName, 1);
|
||||
var pause = SpawnScreen(catalog.PausePanelPrefab, contentRoot, PausePanelName, 2);
|
||||
var result = SpawnScreen(catalog.ResultPanelPrefab, contentRoot, ResultPanelName, 3);
|
||||
|
||||
var mainMenuView = RequireComponent<MainMenuView>(mainMenu.transform, MainMenuPanelName);
|
||||
if (mainMenuView != null)
|
||||
{
|
||||
mainMenuView.BindRoot(mainMenu);
|
||||
}
|
||||
|
||||
var boardView = RequireComponent<BoardView>(board.transform, BoardGridName);
|
||||
var pauseView = RequireComponent<PauseView>(pause.transform, PausePanelName);
|
||||
var resultView = RequireComponent<ResultView>(result.transform, ResultPanelName);
|
||||
|
||||
var refs = new MinesweeperScreenRefs(
|
||||
mainMenuView,
|
||||
boardView,
|
||||
pauseView,
|
||||
resultView);
|
||||
|
||||
mainMenu.SetActive(false);
|
||||
board.SetActive(false);
|
||||
pause.SetActive(false);
|
||||
result.SetActive(false);
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
private static GameObject SpawnScreen(GameObject prefab, Transform parent, string expectedName, int siblingIndex)
|
||||
{
|
||||
var instance = Object.Instantiate(prefab, parent, false);
|
||||
instance.name = expectedName;
|
||||
instance.transform.SetSiblingIndex(siblingIndex);
|
||||
Stretch(instance.GetComponent<RectTransform>());
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static void ClearContent(Transform contentRoot)
|
||||
{
|
||||
for (var i = contentRoot.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
var child = contentRoot.GetChild(i);
|
||||
child.SetParent(null, false);
|
||||
Object.Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private static T RequireComponent<T>(Transform root, string ownerName) where T : Component
|
||||
{
|
||||
var component = root.GetComponent<T>();
|
||||
if (component == null)
|
||||
{
|
||||
Debug.LogError($"Minesweeper screen bootstrap failed: '{ownerName}' is missing {typeof(T).Name}.");
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
private static void Stretch(RectTransform rectTransform)
|
||||
{
|
||||
if (rectTransform == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
rectTransform.anchorMin = Vector2.zero;
|
||||
rectTransform.anchorMax = Vector2.one;
|
||||
rectTransform.offsetMin = Vector2.zero;
|
||||
rectTransform.offsetMax = Vector2.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38a228c86699cdb499351cf807842dfb
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3895ae14930e10841a7562a478c871ec
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,193 @@
|
||||
using Minesweeper.Commands;
|
||||
using Minesweeper.Core;
|
||||
using Minesweeper.Presentation.Factories;
|
||||
using Minesweeper.Presentation.ReadModels;
|
||||
using Minesweeper.Presentation.Views;
|
||||
|
||||
namespace Minesweeper.Presentation.Presenters
|
||||
{
|
||||
public sealed class GamePresenter : IPresenter
|
||||
{
|
||||
private readonly IGameCommandDispatcher commandDispatcher;
|
||||
private readonly ICellViewFactory cellViewFactory;
|
||||
private readonly IGamePauseService pauseService;
|
||||
private readonly IGameReadModel readModel;
|
||||
private readonly IGameStateService gameStateService;
|
||||
private readonly TopPanelPresenter topPanelPresenter;
|
||||
private readonly IBoardView boardView;
|
||||
private readonly IPauseView pauseView;
|
||||
private readonly IResultView resultView;
|
||||
private bool boardBuilt;
|
||||
|
||||
public GamePresenter(IGameCommandDispatcher commandDispatcher, ICellViewFactory cellViewFactory, IGamePauseService pauseService, IGameReadModel readModel, IGameStateService gameStateService, TopPanelPresenter topPanelPresenter, IBoardView boardView, IPauseView pauseView, IResultView resultView)
|
||||
{
|
||||
this.commandDispatcher = commandDispatcher;
|
||||
this.cellViewFactory = cellViewFactory;
|
||||
this.pauseService = pauseService;
|
||||
this.readModel = readModel;
|
||||
this.gameStateService = gameStateService;
|
||||
this.topPanelPresenter = topPanelPresenter;
|
||||
this.boardView = boardView;
|
||||
this.pauseView = pauseView;
|
||||
this.resultView = resultView;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
boardView.CellPressStarted += OnCellPressStarted;
|
||||
boardView.CellPressEnded += OnCellPressEnded;
|
||||
boardView.PauseRequested += OnPauseRequested;
|
||||
boardView.CellOpenRequested += OnCellOpenRequested;
|
||||
boardView.CellFlagRequested += OnCellFlagRequested;
|
||||
pauseView.RestartRequested += OnRestartRequested;
|
||||
pauseView.ResumeRequested += OnResumeRequested;
|
||||
pauseView.GoToMenuRequested += OnGoToMenuRequested;
|
||||
resultView.RestartRequested += OnRestartRequested;
|
||||
resultView.GoToMenuRequested += OnGoToMenuRequested;
|
||||
gameStateService.StateChanged += OnStateChanged;
|
||||
pauseService.PauseChanged += OnPauseChanged;
|
||||
OnStateChanged(gameStateService.Current);
|
||||
OnPauseChanged(pauseService.IsPaused);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
boardView.CellPressStarted -= OnCellPressStarted;
|
||||
boardView.CellPressEnded -= OnCellPressEnded;
|
||||
boardView.PauseRequested -= OnPauseRequested;
|
||||
boardView.CellOpenRequested -= OnCellOpenRequested;
|
||||
boardView.CellFlagRequested -= OnCellFlagRequested;
|
||||
pauseView.RestartRequested -= OnRestartRequested;
|
||||
pauseView.ResumeRequested -= OnResumeRequested;
|
||||
pauseView.GoToMenuRequested -= OnGoToMenuRequested;
|
||||
resultView.RestartRequested -= OnRestartRequested;
|
||||
resultView.GoToMenuRequested -= OnGoToMenuRequested;
|
||||
gameStateService.StateChanged -= OnStateChanged;
|
||||
pauseService.PauseChanged -= OnPauseChanged;
|
||||
}
|
||||
|
||||
private void OnCellOpenRequested(int x, int y)
|
||||
{
|
||||
topPanelPresenter.SetCellPressActive(false);
|
||||
commandDispatcher.Dispatch(new OpenCellCommand(x, y));
|
||||
RefreshBoard();
|
||||
topPanelPresenter.RefreshCounters();
|
||||
UpdateBoardInput();
|
||||
}
|
||||
|
||||
private void OnCellFlagRequested(int x, int y)
|
||||
{
|
||||
commandDispatcher.Dispatch(new ToggleFlagCommand(x, y));
|
||||
RefreshBoard();
|
||||
topPanelPresenter.RefreshCounters();
|
||||
UpdateBoardInput();
|
||||
}
|
||||
|
||||
private void OnCellPressStarted()
|
||||
{
|
||||
topPanelPresenter.SetCellPressActive(true);
|
||||
}
|
||||
|
||||
private void OnCellPressEnded()
|
||||
{
|
||||
topPanelPresenter.SetCellPressActive(false);
|
||||
}
|
||||
|
||||
private void OnStateChanged(GameState state)
|
||||
{
|
||||
if (state == GameState.FieldSelection)
|
||||
{
|
||||
boardBuilt = false;
|
||||
boardView.Hide();
|
||||
pauseView.Hide();
|
||||
resultView.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
boardView.Show();
|
||||
|
||||
if (state == GameState.Won || state == GameState.Lost)
|
||||
{
|
||||
pauseView.Hide();
|
||||
resultView.Show(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultView.Hide();
|
||||
}
|
||||
|
||||
if (!boardBuilt || state == GameState.Preparing)
|
||||
{
|
||||
RebuildBoard();
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshBoard();
|
||||
}
|
||||
|
||||
UpdateBoardInput();
|
||||
}
|
||||
|
||||
private void OnPauseChanged(bool isPaused)
|
||||
{
|
||||
if (isPaused && gameStateService.Current == GameState.Playing)
|
||||
{
|
||||
pauseView.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
pauseView.Hide();
|
||||
}
|
||||
|
||||
UpdateBoardInput();
|
||||
}
|
||||
|
||||
private void RebuildBoard()
|
||||
{
|
||||
var cells = readModel.GetCells();
|
||||
boardView.Rebuild(cells, readModel.Width, readModel.Height, cellViewFactory, IsFinalState());
|
||||
boardBuilt = true;
|
||||
topPanelPresenter.RefreshCounters();
|
||||
UpdateBoardInput();
|
||||
}
|
||||
|
||||
private void RefreshBoard()
|
||||
{
|
||||
boardView.Refresh(readModel.GetCells(), IsFinalState());
|
||||
}
|
||||
|
||||
private bool IsFinalState()
|
||||
{
|
||||
var state = gameStateService.Current;
|
||||
return state == GameState.Lost || state == GameState.Won;
|
||||
}
|
||||
|
||||
private void UpdateBoardInput()
|
||||
{
|
||||
var state = gameStateService.Current;
|
||||
boardView.SetInputEnabled(!pauseService.IsPaused && (state == GameState.Preparing || state == GameState.Playing));
|
||||
}
|
||||
|
||||
private void OnRestartRequested()
|
||||
{
|
||||
commandDispatcher.Dispatch(new RestartCommand());
|
||||
RebuildBoard();
|
||||
topPanelPresenter.RefreshCounters();
|
||||
}
|
||||
|
||||
private void OnGoToMenuRequested()
|
||||
{
|
||||
commandDispatcher.Dispatch(new GoToMenuCommand());
|
||||
}
|
||||
|
||||
private void OnResumeRequested()
|
||||
{
|
||||
commandDispatcher.Dispatch(new ResumeCommand());
|
||||
}
|
||||
|
||||
private void OnPauseRequested()
|
||||
{
|
||||
commandDispatcher.Dispatch(new PauseCommand());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f85646fc4851054e9efffa3ab1f6853
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Minesweeper.Presentation.Presenters
|
||||
{
|
||||
public interface IPresenter : IDisposable
|
||||
{
|
||||
void Initialize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0fcee9aa500b62429eb4ffa2249de9c
|
||||
@@ -0,0 +1,81 @@
|
||||
using Minesweeper.Commands;
|
||||
using Minesweeper.Config;
|
||||
using Minesweeper.Core;
|
||||
using Minesweeper.Presentation.Views;
|
||||
|
||||
namespace Minesweeper.Presentation.Presenters
|
||||
{
|
||||
public sealed class MainMenuPresenter : IPresenter
|
||||
{
|
||||
private readonly IGameCommandDispatcher commandDispatcher;
|
||||
private readonly MinesweeperGameConfig config;
|
||||
private readonly IGameSettingsService settingsService;
|
||||
private readonly IGameStateService gameStateService;
|
||||
private readonly IMainMenuView view;
|
||||
|
||||
public MainMenuPresenter(IGameCommandDispatcher commandDispatcher, MinesweeperGameConfig config, IGameSettingsService settingsService, IGameStateService gameStateService, IMainMenuView view = null)
|
||||
{
|
||||
this.commandDispatcher = commandDispatcher;
|
||||
this.config = config;
|
||||
this.settingsService = settingsService;
|
||||
this.gameStateService = gameStateService;
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
if (view != null)
|
||||
{
|
||||
view.StartClicked += OnStartClicked;
|
||||
view.SizeChanged += OnSizeChanged;
|
||||
gameStateService.StateChanged += OnStateChanged;
|
||||
RefreshMenuValues(settingsService.Current);
|
||||
OnStateChanged(gameStateService.Current);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (view != null)
|
||||
{
|
||||
view.StartClicked -= OnStartClicked;
|
||||
view.SizeChanged -= OnSizeChanged;
|
||||
gameStateService.StateChanged -= OnStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStartClicked()
|
||||
{
|
||||
settingsService.ApplyAndSaveIfChanged(view.SelectedSettings);
|
||||
commandDispatcher.Dispatch(new StartGameCommand());
|
||||
}
|
||||
|
||||
private void OnSizeChanged()
|
||||
{
|
||||
var selected = settingsService.Clamp(view.SelectedSettings);
|
||||
var maxMines = settingsService.GetMaxMines(selected.SizeX, selected.SizeY);
|
||||
view.ConfigureMines(1, maxMines, selected.MinesCount);
|
||||
}
|
||||
|
||||
private void OnStateChanged(GameState state)
|
||||
{
|
||||
if (state == GameState.FieldSelection)
|
||||
{
|
||||
RefreshMenuValues(settingsService.Current);
|
||||
view.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
view.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshMenuValues(GameSettingsValue settings)
|
||||
{
|
||||
var clamped = settingsService.Clamp(settings);
|
||||
view.ConfigureSizeX(config.MinSizeX, config.MaxSizeX, clamped.SizeX);
|
||||
view.ConfigureSizeY(config.MinSizeY, config.MaxSizeY, clamped.SizeY);
|
||||
view.ConfigureMines(1, settingsService.GetMaxMines(clamped.SizeX, clamped.SizeY), clamped.MinesCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 835a3b63609e9864fa6154be8b8283ad
|
||||
@@ -0,0 +1,130 @@
|
||||
using Minesweeper.Commands;
|
||||
using Minesweeper.Config;
|
||||
using Minesweeper.Core;
|
||||
using Minesweeper.Presentation.ReadModels;
|
||||
using Minesweeper.Presentation.Views;
|
||||
|
||||
namespace Minesweeper.Presentation.Presenters
|
||||
{
|
||||
public sealed class TopPanelPresenter : IPresenter
|
||||
{
|
||||
private readonly IGameCommandDispatcher commandDispatcher;
|
||||
private readonly IGamePauseService pauseService;
|
||||
private readonly IGameReadModel readModel;
|
||||
private readonly IGameStateService gameStateService;
|
||||
private readonly IGameTimerService timerService;
|
||||
private readonly ITopPanelView view;
|
||||
|
||||
public TopPanelPresenter(IGameCommandDispatcher commandDispatcher, IGamePauseService pauseService, IGameReadModel readModel, IGameStateService gameStateService, IGameTimerService timerService, ITopPanelView view)
|
||||
{
|
||||
this.commandDispatcher = commandDispatcher;
|
||||
this.pauseService = pauseService;
|
||||
this.readModel = readModel;
|
||||
this.gameStateService = gameStateService;
|
||||
this.timerService = timerService;
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
view.SmileClicked += OnSmileClicked;
|
||||
gameStateService.StateChanged += OnStateChanged;
|
||||
pauseService.PauseChanged += OnPauseChanged;
|
||||
timerService.TimeChanged += OnTimeChanged;
|
||||
|
||||
RefreshAll();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
view.SmileClicked -= OnSmileClicked;
|
||||
gameStateService.StateChanged -= OnStateChanged;
|
||||
pauseService.PauseChanged -= OnPauseChanged;
|
||||
timerService.TimeChanged -= OnTimeChanged;
|
||||
}
|
||||
|
||||
public void RefreshCounters()
|
||||
{
|
||||
view.SetRemainingMines(readModel.RemainingMinesCount);
|
||||
}
|
||||
|
||||
public void SetCellPressActive(bool active)
|
||||
{
|
||||
var state = gameStateService.Current;
|
||||
if (active && !pauseService.IsPaused && (state == GameState.Preparing || state == GameState.Playing))
|
||||
{
|
||||
view.SetSmile(SmileFaceState.Surprised);
|
||||
return;
|
||||
}
|
||||
|
||||
view.SetSmile(GetSmileState(state));
|
||||
}
|
||||
|
||||
private void RefreshAll()
|
||||
{
|
||||
view.SetActive(true);
|
||||
view.SetRemainingMines(readModel.RemainingMinesCount);
|
||||
view.SetTimer(timerService.ElapsedSeconds);
|
||||
view.SetSmile(GetSmileState(gameStateService.Current));
|
||||
}
|
||||
|
||||
private void OnSmileClicked()
|
||||
{
|
||||
var state = gameStateService.Current;
|
||||
if (state == GameState.FieldSelection)
|
||||
{
|
||||
commandDispatcher.Dispatch(new StartGameCommand());
|
||||
}
|
||||
else if (state == GameState.Playing && !pauseService.IsPaused)
|
||||
{
|
||||
commandDispatcher.Dispatch(new PauseCommand());
|
||||
}
|
||||
else if (state == GameState.Playing && pauseService.IsPaused)
|
||||
{
|
||||
commandDispatcher.Dispatch(new ResumeCommand());
|
||||
}
|
||||
else
|
||||
{
|
||||
commandDispatcher.Dispatch(new RestartCommand());
|
||||
}
|
||||
|
||||
RefreshAll();
|
||||
}
|
||||
|
||||
private void OnStateChanged(GameState state)
|
||||
{
|
||||
if (state == GameState.FieldSelection || state == GameState.Preparing)
|
||||
{
|
||||
view.SetTimer(0f);
|
||||
}
|
||||
|
||||
view.SetSmile(GetSmileState(state));
|
||||
view.SetRemainingMines(readModel.RemainingMinesCount);
|
||||
}
|
||||
|
||||
private void OnPauseChanged(bool isPaused)
|
||||
{
|
||||
view.SetSmile(GetSmileState(gameStateService.Current));
|
||||
}
|
||||
|
||||
private void OnTimeChanged(float seconds)
|
||||
{
|
||||
view.SetTimer(seconds);
|
||||
}
|
||||
|
||||
private SmileFaceState GetSmileState(GameState state)
|
||||
{
|
||||
if (state == GameState.Won)
|
||||
{
|
||||
return SmileFaceState.Cool;
|
||||
}
|
||||
|
||||
if (state == GameState.Lost)
|
||||
{
|
||||
return SmileFaceState.Dead;
|
||||
}
|
||||
|
||||
return SmileFaceState.Smile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3ac57cd2e63497d9d0d7e23659a4d45
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 590e96d08af41e346b49a7ce43641afb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using Minesweeper.Core;
|
||||
|
||||
namespace Minesweeper.Presentation.ReadModels
|
||||
{
|
||||
public sealed class GameReadModel : IGameReadModel
|
||||
{
|
||||
private readonly IBoardService boardService;
|
||||
private readonly IGameSettingsService settingsService;
|
||||
private readonly IGameStateService gameStateService;
|
||||
|
||||
public GameReadModel(IBoardService boardService, IGameSettingsService settingsService, IGameStateService gameStateService)
|
||||
{
|
||||
this.boardService = boardService;
|
||||
this.settingsService = settingsService;
|
||||
this.gameStateService = gameStateService;
|
||||
}
|
||||
|
||||
public GameState State => gameStateService.Current;
|
||||
public int Width => boardService.Width > 0 ? boardService.Width : settingsService.SizeX;
|
||||
public int Height => boardService.Height > 0 ? boardService.Height : settingsService.SizeY;
|
||||
public int MinesCount => boardService.MinesCount > 0 ? boardService.MinesCount : settingsService.MinesCount;
|
||||
public int FlaggedCellsCount => CountFlaggedCells();
|
||||
public int RemainingMinesCount => MinesCount - FlaggedCellsCount;
|
||||
|
||||
public bool TryGetCell(int x, int y, out BoardCellData cell)
|
||||
{
|
||||
return boardService.TryGetCell(x, y, out cell);
|
||||
}
|
||||
|
||||
public IReadOnlyList<BoardCellData> GetCells()
|
||||
{
|
||||
return boardService.GetCells();
|
||||
}
|
||||
|
||||
private int CountFlaggedCells()
|
||||
{
|
||||
var cells = boardService.GetCells();
|
||||
var count = 0;
|
||||
for (var i = 0; i < cells.Count; i++)
|
||||
{
|
||||
if (cells[i].IsFlagged)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b2a816449e034a4f9635960881ec852
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using Minesweeper.Core;
|
||||
|
||||
namespace Minesweeper.Presentation.ReadModels
|
||||
{
|
||||
public interface IGameReadModel
|
||||
{
|
||||
GameState State { get; }
|
||||
int Width { get; }
|
||||
int Height { get; }
|
||||
int MinesCount { get; }
|
||||
int FlaggedCellsCount { get; }
|
||||
int RemainingMinesCount { get; }
|
||||
|
||||
bool TryGetCell(int x, int y, out BoardCellData cell);
|
||||
IReadOnlyList<BoardCellData> GetCells();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5c921f20dd6f1b40a1f73746265a63d
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user