[Add] Basic project architecture

This commit is contained in:
2026-06-06 20:53:30 +07:00
parent 9ebedb12ec
commit 8ed9cc655f
79 changed files with 1080 additions and 9 deletions
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 167a3599d465fb2438304d672c850377
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b4e8d5c36f36bb443b640a85df3e7077, type: 3}
m_Name: MinesweeperGameConfig
m_EditorClassIdentifier: Assembly-CSharp::Minesweeper.Config.MinesweeperGameConfig
width: 9
height: 9
minesCount: 10
restartKey: 114
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4c24a7c2a548eff4fb21fa4a4bf3e741
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d7d59007b2f263148ae29878cc0dd0a5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -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,92 @@
using Minesweeper.Core;
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 IGameStateService gameStateService;
public StartGameCommandHandler(IGameStateService gameStateService)
{
this.gameStateService = gameStateService;
}
public void Handle(StartGameCommand command)
{
gameStateService.SetState(GameState.Preparing);
}
}
public sealed class OpenCellCommandHandler : IGameCommandHandler<OpenCellCommand>
{
public void Handle(OpenCellCommand command)
{
}
}
public sealed class ToggleFlagCommandHandler : IGameCommandHandler<ToggleFlagCommand>
{
public void Handle(ToggleFlagCommand command)
{
}
}
public sealed class RestartCommandHandler : IGameCommandHandler<RestartCommand>
{
private readonly IGameStateService gameStateService;
public RestartCommandHandler(IGameStateService gameStateService)
{
this.gameStateService = gameStateService;
}
public void Handle(RestartCommand command)
{
gameStateService.SetState(GameState.Preparing);
}
}
public sealed class PauseCommandHandler : IGameCommandHandler<PauseCommand>
{
public void Handle(PauseCommand command)
{
}
}
public sealed class ResumeCommandHandler : IGameCommandHandler<ResumeCommand>
{
public void Handle(ResumeCommand command)
{
}
}
public sealed class GoToMenuCommandHandler : IGameCommandHandler<GoToMenuCommand>
{
private readonly IGameStateService gameStateService;
public GoToMenuCommandHandler(IGameStateService gameStateService)
{
this.gameStateService = gameStateService;
}
public void Handle(GoToMenuCommand command)
{
gameStateService.SetState(GameState.FieldSelection);
}
}
}
@@ -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
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a904ba5489d53724692eb73dd2a68f2e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,20 @@
using UnityEngine;
namespace Minesweeper.Config
{
[CreateAssetMenu(fileName = "MinesweeperGameConfig", menuName = "Minesweeper/Game Config")]
public sealed class MinesweeperGameConfig : ScriptableObject
{
[SerializeField, Min(1)] private int width = 9;
[SerializeField, Min(1)] private int height = 9;
[SerializeField, Min(1)] private int minesCount = 10;
[SerializeField] private KeyCode restartKey = KeyCode.R;
public int Width => width;
public int Height => height;
public int MinesCount => minesCount;
public KeyCode RestartKey => restartKey;
public bool IsValid => width > 0 && height > 0 && minesCount > 0 && minesCount < width * height;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b4e8d5c36f36bb443b640a85df3e7077
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 391f2b5ec87a6634694fe2c31faee82e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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,22 @@
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);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cf69805439993c14887ea7bb9b15bd02
@@ -0,0 +1,13 @@
using System;
namespace Minesweeper.Core
{
public interface IGameStateService
{
event Action<GameState> StateChanged;
GameState Current { get; }
void SetState(GameState state);
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7aea04a8c0e8d3a4e8d991a4348430db
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ebd50c14109e96541aa49542a07986aa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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,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,30 @@
using System;
using Minesweeper.Presentation.Presenters;
using VContainer.Unity;
namespace Minesweeper.Infrastructure
{
public sealed class MinesweeperEntryPoint : IStartable, IDisposable
{
private readonly MainMenuPresenter mainMenuPresenter;
private readonly GamePresenter gamePresenter;
public MinesweeperEntryPoint(MainMenuPresenter mainMenuPresenter, GamePresenter gamePresenter)
{
this.mainMenuPresenter = mainMenuPresenter;
this.gamePresenter = gamePresenter;
}
public void Start()
{
mainMenuPresenter.Initialize();
gamePresenter.Initialize();
}
public void Dispose()
{
gamePresenter.Dispose();
mainMenuPresenter.Dispose();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5eae713b4d801be4996a70d4b630eeee
@@ -0,0 +1,54 @@
using Minesweeper.Commands;
using Minesweeper.Config;
using Minesweeper.Core;
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;
protected override void Configure(IContainerBuilder builder)
{
builder.RegisterInstance(GetConfig());
builder.Register<GameStateService>(Lifetime.Singleton).As<IGameStateService>();
builder.Register<GameReadModel>(Lifetime.Singleton).As<IGameReadModel>();
builder.Register<GameStateViewAdapter>(Lifetime.Singleton).As<IGameStateViewAdapter>();
builder.Register<CellViewFactory>(Lifetime.Singleton).As<ICellViewFactory>();
builder.Register<NullMainMenuView>(Lifetime.Singleton).As<IMainMenuView>();
builder.Register<NullGameView>(Lifetime.Singleton).As<IGameView>();
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<GamePresenter>(Lifetime.Singleton);
builder.RegisterEntryPoint<MinesweeperEntryPoint>();
}
private MinesweeperGameConfig GetConfig()
{
if (gameConfig != null)
{
return gameConfig;
}
return ScriptableObject.CreateInstance<MinesweeperGameConfig>();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d4f9b0c2ad803d84382fbf03ba3096fa
@@ -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,10 @@
namespace Minesweeper.Presentation.Factories
{
public sealed class CellViewFactory : ICellViewFactory
{
public string BuildCellName(int x, int y, int value, bool isMine)
{
return $"bt_{x}_{y}_{(isMine ? "M" : value.ToString())}";
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 681a16a970ddf3546bb4f99d93a184ca
@@ -0,0 +1,7 @@
namespace Minesweeper.Presentation.Factories
{
public interface ICellViewFactory
{
string BuildCellName(int x, int y, int value, bool isMine);
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 34c31a0f01c730440a2bcdac0f775dd8
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3895ae14930e10841a7562a478c871ec
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,50 @@
using Minesweeper.Commands;
using Minesweeper.Presentation.ReadModels;
using Minesweeper.Presentation.Views;
namespace Minesweeper.Presentation.Presenters
{
public sealed class GamePresenter : IPresenter
{
private readonly IGameCommandDispatcher commandDispatcher;
private readonly IGameReadModel readModel;
private readonly IGameView view;
public GamePresenter(IGameCommandDispatcher commandDispatcher, IGameReadModel readModel, IGameView view = null)
{
this.commandDispatcher = commandDispatcher;
this.readModel = readModel;
this.view = view;
}
public void Initialize()
{
_ = readModel.State;
if (view != null)
{
view.RestartRequested += OnRestartRequested;
view.GoToMenuRequested += OnGoToMenuRequested;
}
}
public void Dispose()
{
if (view != null)
{
view.RestartRequested -= OnRestartRequested;
view.GoToMenuRequested -= OnGoToMenuRequested;
}
}
private void OnRestartRequested()
{
commandDispatcher.Dispatch(new RestartCommand());
}
private void OnGoToMenuRequested()
{
commandDispatcher.Dispatch(new GoToMenuCommand());
}
}
}
@@ -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,38 @@
using Minesweeper.Commands;
using Minesweeper.Presentation.Views;
namespace Minesweeper.Presentation.Presenters
{
public sealed class MainMenuPresenter : IPresenter
{
private readonly IGameCommandDispatcher commandDispatcher;
private readonly IMainMenuView view;
public MainMenuPresenter(IGameCommandDispatcher commandDispatcher, IMainMenuView view = null)
{
this.commandDispatcher = commandDispatcher;
this.view = view;
}
public void Initialize()
{
if (view != null)
{
view.StartClicked += OnStartClicked;
}
}
public void Dispose()
{
if (view != null)
{
view.StartClicked -= OnStartClicked;
}
}
private void OnStartClicked()
{
commandDispatcher.Dispatch(new StartGameCommand());
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 835a3b63609e9864fa6154be8b8283ad
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 590e96d08af41e346b49a7ce43641afb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
using Minesweeper.Config;
using Minesweeper.Core;
namespace Minesweeper.Presentation.ReadModels
{
public sealed class GameReadModel : IGameReadModel
{
private readonly MinesweeperGameConfig config;
private readonly IGameStateService gameStateService;
public GameReadModel(MinesweeperGameConfig config, IGameStateService gameStateService)
{
this.config = config;
this.gameStateService = gameStateService;
}
public GameState State => gameStateService.Current;
public int Width => config.Width;
public int Height => config.Height;
public int MinesCount => config.MinesCount;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1b2a816449e034a4f9635960881ec852
@@ -0,0 +1,12 @@
using Minesweeper.Core;
namespace Minesweeper.Presentation.ReadModels
{
public interface IGameReadModel
{
GameState State { get; }
int Width { get; }
int Height { get; }
int MinesCount { get; }
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e5c921f20dd6f1b40a1f73746265a63d
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0e2357d56da983b478c1cebe1e3ac363
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
using System;
namespace Minesweeper.Presentation.Views
{
public interface IGameView : IView
{
event Action RestartRequested;
event Action GoToMenuRequested;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a8c5423ea37354a4e82b05aadfbf239f
@@ -0,0 +1,9 @@
using System;
namespace Minesweeper.Presentation.Views
{
public interface IMainMenuView : IView
{
event Action StartClicked;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 66cad179080f23a479c3418932137653
@@ -0,0 +1,6 @@
namespace Minesweeper.Presentation.Views
{
public interface IView
{
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 66a8b79ae5812744680f9e386b007144
@@ -0,0 +1,19 @@
using System;
namespace Minesweeper.Presentation.Views
{
public sealed class NullGameView : IGameView
{
public event Action RestartRequested
{
add { }
remove { }
}
public event Action GoToMenuRequested
{
add { }
remove { }
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c800a42df535f9347bea10f164fd2e15
@@ -0,0 +1,13 @@
using System;
namespace Minesweeper.Presentation.Views
{
public sealed class NullMainMenuView : IMainMenuView
{
public event Action StartClicked
{
add { }
remove { }
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6b41dd95488a1db42adccef0225d2f89