[Add] Menu with configs and size fix
This commit is contained in:
@@ -12,7 +12,7 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: b4e8d5c36f36bb443b640a85df3e7077, type: 3}
|
m_Script: {fileID: 11500000, guid: b4e8d5c36f36bb443b640a85df3e7077, type: 3}
|
||||||
m_Name: MinesweeperGameConfig
|
m_Name: MinesweeperGameConfig
|
||||||
m_EditorClassIdentifier: Assembly-CSharp::Minesweeper.Config.MinesweeperGameConfig
|
m_EditorClassIdentifier: Assembly-CSharp::Minesweeper.Config.MinesweeperGameConfig
|
||||||
<Width>k__BackingField: 9
|
<MinSizeX>k__BackingField: 2
|
||||||
<Height>k__BackingField: 9
|
<MaxSizeX>k__BackingField: 50
|
||||||
<MinesCount>k__BackingField: 1
|
<MinSizeY>k__BackingField: 2
|
||||||
<RestartKey>k__BackingField: 114
|
<MaxSizeY>k__BackingField: 50
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ RectTransform:
|
|||||||
m_AnchorMin: {x: 0, y: 0}
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
m_AnchorMax: {x: 1, y: 1}
|
m_AnchorMax: {x: 1, y: 1}
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
m_SizeDelta: {x: -15, y: -15}
|
m_SizeDelta: {x: -8, y: -8}
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
--- !u!1 &3798432596530835849
|
--- !u!1 &3798432596530835849
|
||||||
GameObject:
|
GameObject:
|
||||||
@@ -162,7 +162,7 @@ RectTransform:
|
|||||||
m_AnchorMin: {x: 0, y: 0}
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
m_AnchorMax: {x: 1, y: 1}
|
m_AnchorMax: {x: 1, y: 1}
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
m_SizeDelta: {x: 30, y: 30}
|
m_SizeDelta: {x: 0, y: 0}
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
--- !u!222 &4574882275002937660
|
--- !u!222 &4574882275002937660
|
||||||
CanvasRenderer:
|
CanvasRenderer:
|
||||||
@@ -219,12 +219,12 @@ MonoBehaviour:
|
|||||||
m_faceColor:
|
m_faceColor:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
rgba: 4294967295
|
rgba: 4294967295
|
||||||
m_fontSize: 54.6
|
m_fontSize: 35.8
|
||||||
m_fontSizeBase: 15
|
m_fontSizeBase: 15
|
||||||
m_fontWeight: 400
|
m_fontWeight: 400
|
||||||
m_enableAutoSizing: 1
|
m_enableAutoSizing: 1
|
||||||
m_fontSizeMin: 18
|
m_fontSizeMin: 1
|
||||||
m_fontSizeMax: 72
|
m_fontSizeMax: 300
|
||||||
m_fontStyle: 0
|
m_fontStyle: 0
|
||||||
m_HorizontalAlignment: 2
|
m_HorizontalAlignment: 2
|
||||||
m_VerticalAlignment: 512
|
m_VerticalAlignment: 512
|
||||||
@@ -258,7 +258,7 @@ MonoBehaviour:
|
|||||||
m_VertexBufferAutoSizeReduction: 0
|
m_VertexBufferAutoSizeReduction: 0
|
||||||
m_useMaxVisibleDescender: 1
|
m_useMaxVisibleDescender: 1
|
||||||
m_pageToDisplay: 1
|
m_pageToDisplay: 1
|
||||||
m_margin: {x: 2, y: 2, z: 2, w: 2}
|
m_margin: {x: 1, y: 1, z: 1, w: 1}
|
||||||
m_isUsingLegacyAnimationComponent: 0
|
m_isUsingLegacyAnimationComponent: 0
|
||||||
m_isVolumetricText: 0
|
m_isVolumetricText: 0
|
||||||
m_hasFontAssetChanged: 0
|
m_hasFontAssetChanged: 0
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ GameObject:
|
|||||||
- component: {fileID: 2154271443323945884}
|
- component: {fileID: 2154271443323945884}
|
||||||
- component: {fileID: 6204093840248163070}
|
- component: {fileID: 6204093840248163070}
|
||||||
m_Layer: 5
|
m_Layer: 5
|
||||||
m_Name: Text (Mine) (1)
|
m_Name: Text (Max)
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
m_Icon: {fileID: 0}
|
m_Icon: {fileID: 0}
|
||||||
m_NavMeshLayer: 0
|
m_NavMeshLayer: 0
|
||||||
@@ -722,10 +722,10 @@ RectTransform:
|
|||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 6299792064077961574}
|
m_Father: {fileID: 6299792064077961574}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
m_AnchorMin: {x: 0.5, y: 1}
|
m_AnchorMin: {x: 0, y: 1}
|
||||||
m_AnchorMax: {x: 0.5, y: 1}
|
m_AnchorMax: {x: 1, y: 1}
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
m_SizeDelta: {x: 250, y: 48}
|
m_SizeDelta: {x: 0, y: 48}
|
||||||
m_Pivot: {x: 0.5, y: 0}
|
m_Pivot: {x: 0.5, y: 0}
|
||||||
--- !u!222 &7925194886356738603
|
--- !u!222 &7925194886356738603
|
||||||
CanvasRenderer:
|
CanvasRenderer:
|
||||||
@@ -996,10 +996,10 @@ RectTransform:
|
|||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 4947738170035721252}
|
m_Father: {fileID: 4947738170035721252}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
m_AnchorMin: {x: 0.5, y: 1}
|
m_AnchorMin: {x: 0, y: 1}
|
||||||
m_AnchorMax: {x: 0.5, y: 1}
|
m_AnchorMax: {x: 1, y: 1}
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
m_SizeDelta: {x: 250, y: 48}
|
m_SizeDelta: {x: 0, y: 48}
|
||||||
m_Pivot: {x: 0.5, y: 0}
|
m_Pivot: {x: 0.5, y: 0}
|
||||||
--- !u!222 &4764443876518541376
|
--- !u!222 &4764443876518541376
|
||||||
CanvasRenderer:
|
CanvasRenderer:
|
||||||
@@ -1208,10 +1208,10 @@ RectTransform:
|
|||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 8549637007489853463}
|
m_Father: {fileID: 8549637007489853463}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
m_AnchorMin: {x: 0.5, y: 1}
|
m_AnchorMin: {x: 0, y: 1}
|
||||||
m_AnchorMax: {x: 0.5, y: 1}
|
m_AnchorMax: {x: 1, y: 1}
|
||||||
m_AnchoredPosition: {x: 0, y: 0}
|
m_AnchoredPosition: {x: 0, y: 1}
|
||||||
m_SizeDelta: {x: 250, y: 48}
|
m_SizeDelta: {x: 0, y: 48}
|
||||||
m_Pivot: {x: 0.5, y: 0}
|
m_Pivot: {x: 0.5, y: 0}
|
||||||
--- !u!222 &929277193634291900
|
--- !u!222 &929277193634291900
|
||||||
CanvasRenderer:
|
CanvasRenderer:
|
||||||
@@ -1421,8 +1421,8 @@ RectTransform:
|
|||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
m_AnchorMin: {x: 0, y: 0}
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
m_AnchorMax: {x: 1, y: 1}
|
m_AnchorMax: {x: 1, y: 1}
|
||||||
m_AnchoredPosition: {x: -391, y: -192}
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
m_SizeDelta: {x: -20, y: 0}
|
m_SizeDelta: {x: 0, y: 0}
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
--- !u!1 &3877081085258266573
|
--- !u!1 &3877081085258266573
|
||||||
GameObject:
|
GameObject:
|
||||||
@@ -1609,7 +1609,7 @@ GameObject:
|
|||||||
- component: {fileID: 7567565223110830849}
|
- component: {fileID: 7567565223110830849}
|
||||||
- component: {fileID: 3264166992253475585}
|
- component: {fileID: 3264166992253475585}
|
||||||
m_Layer: 5
|
m_Layer: 5
|
||||||
m_Name: Text (Mine)
|
m_Name: Text (Min)
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
m_Icon: {fileID: 0}
|
m_Icon: {fileID: 0}
|
||||||
m_NavMeshLayer: 0
|
m_NavMeshLayer: 0
|
||||||
@@ -2028,8 +2028,8 @@ RectTransform:
|
|||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
m_AnchorMin: {x: 0, y: 0}
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
m_AnchorMax: {x: 1, y: 1}
|
m_AnchorMax: {x: 1, y: 1}
|
||||||
m_AnchoredPosition: {x: -391, y: -192}
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
m_SizeDelta: {x: -20, y: 0}
|
m_SizeDelta: {x: 0, y: 0}
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
--- !u!1 &6455657920195163342
|
--- !u!1 &6455657920195163342
|
||||||
GameObject:
|
GameObject:
|
||||||
@@ -2068,7 +2068,7 @@ RectTransform:
|
|||||||
m_AnchorMin: {x: 0, y: 0}
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
m_AnchorMax: {x: 1, y: 1}
|
m_AnchorMax: {x: 1, y: 1}
|
||||||
m_AnchoredPosition: {x: 0, y: -21}
|
m_AnchoredPosition: {x: 0, y: -21}
|
||||||
m_SizeDelta: {x: -120, y: -162}
|
m_SizeDelta: {x: -150, y: -162}
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
--- !u!114 &6446935312079034905
|
--- !u!114 &6446935312079034905
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
@@ -2420,8 +2420,8 @@ MonoBehaviour:
|
|||||||
m_faceColor:
|
m_faceColor:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
rgba: 4294967295
|
rgba: 4294967295
|
||||||
m_fontSize: 57
|
m_fontSize: 54
|
||||||
m_fontSizeBase: 57
|
m_fontSizeBase: 54
|
||||||
m_fontWeight: 400
|
m_fontWeight: 400
|
||||||
m_enableAutoSizing: 0
|
m_enableAutoSizing: 0
|
||||||
m_fontSizeMin: 18
|
m_fontSizeMin: 18
|
||||||
@@ -2664,6 +2664,24 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier: Assembly-CSharp::Minesweeper.Presentation.Views.MainMenuView
|
m_EditorClassIdentifier: Assembly-CSharp::Minesweeper.Presentation.Views.MainMenuView
|
||||||
root: {fileID: 7682962739562644362}
|
root: {fileID: 7682962739562644362}
|
||||||
startButton: {fileID: 3904309382312306706}
|
startButton: {fileID: 3904309382312306706}
|
||||||
|
sizeXSlider:
|
||||||
|
slider: {fileID: 2334871180416613717}
|
||||||
|
minText: {fileID: 4504773017523582976}
|
||||||
|
maxText: {fileID: 6866883084726833735}
|
||||||
|
valueText: {fileID: 4474316482196456833}
|
||||||
|
valueLabel: 'Size X:'
|
||||||
|
sizeYSlider:
|
||||||
|
slider: {fileID: 4051923562216906313}
|
||||||
|
minText: {fileID: 1453579952739003993}
|
||||||
|
maxText: {fileID: 8013579874201281113}
|
||||||
|
valueText: {fileID: 8926983590126121119}
|
||||||
|
valueLabel: 'Size Y:'
|
||||||
|
minesSlider:
|
||||||
|
slider: {fileID: 6456819381917595383}
|
||||||
|
minText: {fileID: 3264166992253475585}
|
||||||
|
maxText: {fileID: 6204093840248163070}
|
||||||
|
valueText: {fileID: 7868341748139096968}
|
||||||
|
valueLabel: 'Mines count:'
|
||||||
--- !u!1 &7723311763242053893
|
--- !u!1 &7723311763242053893
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -2847,8 +2865,8 @@ RectTransform:
|
|||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
m_AnchorMin: {x: 0, y: 0}
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
m_AnchorMax: {x: 1, y: 1}
|
m_AnchorMax: {x: 1, y: 1}
|
||||||
m_AnchoredPosition: {x: -391, y: -192}
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
m_SizeDelta: {x: -20, y: 0}
|
m_SizeDelta: {x: 0, y: 0}
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
--- !u!1 &8648434963355287167
|
--- !u!1 &8648434963355287167
|
||||||
GameObject:
|
GameObject:
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ namespace Minesweeper.Config
|
|||||||
[CreateAssetMenu(fileName = "MinesweeperGameConfig", menuName = "Minesweeper/Game Config")]
|
[CreateAssetMenu(fileName = "MinesweeperGameConfig", menuName = "Minesweeper/Game Config")]
|
||||||
public sealed class MinesweeperGameConfig : ScriptableObject
|
public sealed class MinesweeperGameConfig : ScriptableObject
|
||||||
{
|
{
|
||||||
[field: SerializeField, Min(1)] public int Width { get; private set; } = 9;
|
[field: SerializeField, Min(1)] public int MinSizeX { get; private set; } = 2;
|
||||||
[field: SerializeField, Min(1)] public int Height { get; private set; } = 9;
|
[field: SerializeField, Min(1)] public int MaxSizeX { get; private set; } = 50;
|
||||||
[field: SerializeField, Min(1)] public int MinesCount { get; private set; } = 10;
|
[field: SerializeField, Min(1)] public int MinSizeY { get; private set; } = 2;
|
||||||
[field: SerializeField] public KeyCode RestartKey { get; private set; } = KeyCode.R;
|
[field: SerializeField, Min(1)] public int MaxSizeY { get; private set; } = 50;
|
||||||
|
|
||||||
public bool IsValid => Width > 0 && Height > 0 && MinesCount > 0 && MinesCount < Width * Height;
|
public bool IsValid => MinSizeX > 0 && MinSizeY > 0 && MaxSizeX >= MinSizeX && MaxSizeY >= MinSizeY && MaxSizeX * MaxSizeY > 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Minesweeper.Config;
|
|
||||||
|
|
||||||
namespace Minesweeper.Core
|
namespace Minesweeper.Core
|
||||||
{
|
{
|
||||||
public sealed class BoardService : IBoardService
|
public sealed class BoardService : IBoardService
|
||||||
{
|
{
|
||||||
private const int DefaultWidth = 9;
|
private readonly IGameSettingsService settingsService;
|
||||||
private const int DefaultHeight = 9;
|
|
||||||
private const int DefaultMinesCount = 10;
|
|
||||||
|
|
||||||
private readonly MinesweeperGameConfig config;
|
|
||||||
private readonly Random random = new Random();
|
private readonly Random random = new Random();
|
||||||
private CellData[,] cells;
|
private CellData[,] cells;
|
||||||
|
|
||||||
public BoardService(MinesweeperGameConfig config)
|
public BoardService(IGameSettingsService settingsService)
|
||||||
{
|
{
|
||||||
this.config = config;
|
this.settingsService = settingsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Width { get; private set; }
|
public int Width { get; private set; }
|
||||||
@@ -28,11 +23,9 @@ namespace Minesweeper.Core
|
|||||||
|
|
||||||
public void InitializeEmptyBoard()
|
public void InitializeEmptyBoard()
|
||||||
{
|
{
|
||||||
ResolveConfig(out var width, out var height, out var minesCount);
|
Width = settingsService.SizeX;
|
||||||
|
Height = settingsService.SizeY;
|
||||||
Width = width;
|
MinesCount = Math.Min(settingsService.MinesCount, Width * Height - 1);
|
||||||
Height = height;
|
|
||||||
MinesCount = minesCount;
|
|
||||||
OpenedSafeCellsCount = 0;
|
OpenedSafeCellsCount = 0;
|
||||||
IsGenerated = false;
|
IsGenerated = false;
|
||||||
cells = new CellData[Width, Height];
|
cells = new CellData[Width, Height];
|
||||||
@@ -147,20 +140,6 @@ namespace Minesweeper.Core
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResolveConfig(out int width, out int height, out int minesCount)
|
|
||||||
{
|
|
||||||
width = config.Width;
|
|
||||||
height = config.Height;
|
|
||||||
minesCount = config.MinesCount;
|
|
||||||
|
|
||||||
if (width <= 0 || height <= 0 || minesCount <= 0 || minesCount >= width * height)
|
|
||||||
{
|
|
||||||
width = DefaultWidth;
|
|
||||||
height = DefaultHeight;
|
|
||||||
minesCount = DefaultMinesCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureInitialized()
|
private void EnsureInitialized()
|
||||||
{
|
{
|
||||||
if (cells == null)
|
if (cells == null)
|
||||||
|
|||||||
@@ -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,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
|
||||||
@@ -28,6 +28,8 @@ namespace Minesweeper.Infrastructure
|
|||||||
builder.RegisterInstance(GetConfig());
|
builder.RegisterInstance(GetConfig());
|
||||||
var resolvedUiConfig = GetUiConfig();
|
var resolvedUiConfig = GetUiConfig();
|
||||||
builder.RegisterInstance(resolvedUiConfig);
|
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<GameStateService>(Lifetime.Singleton).As<IGameStateService>();
|
||||||
builder.Register<BoardService>(Lifetime.Singleton).As<IBoardService>();
|
builder.Register<BoardService>(Lifetime.Singleton).As<IBoardService>();
|
||||||
builder.Register<GamePauseService>(Lifetime.Singleton).As<IGamePauseService>();
|
builder.Register<GamePauseService>(Lifetime.Singleton).As<IGamePauseService>();
|
||||||
@@ -86,7 +88,6 @@ namespace Minesweeper.Infrastructure
|
|||||||
builder.Register<ResumeCommandHandler>(Lifetime.Singleton);
|
builder.Register<ResumeCommandHandler>(Lifetime.Singleton);
|
||||||
builder.Register<GoToMenuCommandHandler>(Lifetime.Singleton);
|
builder.Register<GoToMenuCommandHandler>(Lifetime.Singleton);
|
||||||
builder.Register<GameCommandDispatcher>(Lifetime.Singleton).As<IGameCommandDispatcher>();
|
builder.Register<GameCommandDispatcher>(Lifetime.Singleton).As<IGameCommandDispatcher>();
|
||||||
builder.Register<RestartKeyInputService>(Lifetime.Singleton).As<ITickable>();
|
|
||||||
|
|
||||||
builder.Register<MainMenuPresenter>(Lifetime.Singleton);
|
builder.Register<MainMenuPresenter>(Lifetime.Singleton);
|
||||||
builder.Register<TopPanelPresenter>(Lifetime.Singleton);
|
builder.Register<TopPanelPresenter>(Lifetime.Singleton);
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using Minesweeper.Commands;
|
|
||||||
using Minesweeper.Config;
|
|
||||||
using Minesweeper.Core;
|
|
||||||
using UnityEngine.InputSystem;
|
|
||||||
using VContainer.Unity;
|
|
||||||
|
|
||||||
namespace Minesweeper.Infrastructure
|
|
||||||
{
|
|
||||||
public sealed class RestartKeyInputService : ITickable
|
|
||||||
{
|
|
||||||
private readonly IGameCommandDispatcher commandDispatcher;
|
|
||||||
private readonly MinesweeperGameConfig config;
|
|
||||||
private readonly IGameStateService gameStateService;
|
|
||||||
|
|
||||||
public RestartKeyInputService(IGameCommandDispatcher commandDispatcher, MinesweeperGameConfig config, IGameStateService gameStateService)
|
|
||||||
{
|
|
||||||
this.commandDispatcher = commandDispatcher;
|
|
||||||
this.config = config;
|
|
||||||
this.gameStateService = gameStateService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Tick()
|
|
||||||
{
|
|
||||||
var keyboard = Keyboard.current;
|
|
||||||
if (keyboard == null || !IsRestartPressed(keyboard))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameStateService.Current == GameState.FieldSelection)
|
|
||||||
{
|
|
||||||
commandDispatcher.Dispatch(new StartGameCommand());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
commandDispatcher.Dispatch(new RestartCommand());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsRestartPressed(Keyboard keyboard)
|
|
||||||
{
|
|
||||||
if (config.RestartKey == UnityEngine.KeyCode.R)
|
|
||||||
{
|
|
||||||
return keyboard.rKey.wasPressedThisFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: c87c15d092dd420b85a09cc786496948
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -11,6 +11,7 @@ namespace Minesweeper.Presentation.Factories
|
|||||||
{
|
{
|
||||||
private const string ContentImagePath = "Content/Image";
|
private const string ContentImagePath = "Content/Image";
|
||||||
private const string ContentLabelPath = "Content/Text (TMP)";
|
private const string ContentLabelPath = "Content/Text (TMP)";
|
||||||
|
private const string ContentPath = "Content";
|
||||||
|
|
||||||
private readonly MinesweeperUiConfig uiConfig;
|
private readonly MinesweeperUiConfig uiConfig;
|
||||||
|
|
||||||
@@ -33,9 +34,10 @@ namespace Minesweeper.Presentation.Factories
|
|||||||
|
|
||||||
var button = go.GetComponent<Button>();
|
var button = go.GetComponent<Button>();
|
||||||
var backgroundImage = go.GetComponent<Image>();
|
var backgroundImage = go.GetComponent<Image>();
|
||||||
|
var contentRoot = FindComponent<RectTransform>(go.transform, ContentPath);
|
||||||
var contentImage = FindComponent<Image>(go.transform, ContentImagePath);
|
var contentImage = FindComponent<Image>(go.transform, ContentImagePath);
|
||||||
var label = FindComponent<TMP_Text>(go.transform, ContentLabelPath);
|
var label = FindComponent<TMP_Text>(go.transform, ContentLabelPath);
|
||||||
view.Bind(button, backgroundImage, contentImage, label);
|
view.Bind(button, backgroundImage, contentRoot, contentImage, label);
|
||||||
view.AutoBind();
|
view.AutoBind();
|
||||||
view.Initialize(cell.X, cell.Y);
|
view.Initialize(cell.X, cell.Y);
|
||||||
return view;
|
return view;
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ namespace Minesweeper.Presentation.Factories
|
|||||||
private const string BoardGridName = "BoardGrid";
|
private const string BoardGridName = "BoardGrid";
|
||||||
private const string PausePanelName = "PausePanel";
|
private const string PausePanelName = "PausePanel";
|
||||||
private const string ResultPanelName = "ResultPanel";
|
private const string ResultPanelName = "ResultPanel";
|
||||||
private const string StartButtonPath = "StartButton";
|
|
||||||
private const string RestartButtonPath = "RestartButton";
|
private const string RestartButtonPath = "RestartButton";
|
||||||
private const string ContinueButtonPath = "ContinueButton";
|
private const string ContinueButtonPath = "ContinueButton";
|
||||||
private const string MainMenuButtonPath = "MainMenuButton";
|
private const string MainMenuButtonPath = "MainMenuButton";
|
||||||
@@ -40,7 +39,7 @@ namespace Minesweeper.Presentation.Factories
|
|||||||
var result = SpawnScreen(catalog.ResultPanelPrefab, contentRoot, ResultPanelName, 3);
|
var result = SpawnScreen(catalog.ResultPanelPrefab, contentRoot, ResultPanelName, 3);
|
||||||
|
|
||||||
var mainMenuView = RequireComponent<MainMenuView>(mainMenu.transform, MainMenuPanelName);
|
var mainMenuView = RequireComponent<MainMenuView>(mainMenu.transform, MainMenuPanelName);
|
||||||
mainMenuView.Bind(mainMenu, RequireChildComponent<Button>(mainMenu.transform, StartButtonPath));
|
mainMenuView.BindRoot(mainMenu);
|
||||||
|
|
||||||
var refs = new MinesweeperScreenRefs(
|
var refs = new MinesweeperScreenRefs(
|
||||||
mainMenuView,
|
mainMenuView,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Minesweeper.Commands;
|
using Minesweeper.Commands;
|
||||||
|
using Minesweeper.Config;
|
||||||
using Minesweeper.Core;
|
using Minesweeper.Core;
|
||||||
using Minesweeper.Presentation.Views;
|
using Minesweeper.Presentation.Views;
|
||||||
|
|
||||||
@@ -7,12 +8,16 @@ namespace Minesweeper.Presentation.Presenters
|
|||||||
public sealed class MainMenuPresenter : IPresenter
|
public sealed class MainMenuPresenter : IPresenter
|
||||||
{
|
{
|
||||||
private readonly IGameCommandDispatcher commandDispatcher;
|
private readonly IGameCommandDispatcher commandDispatcher;
|
||||||
|
private readonly MinesweeperGameConfig config;
|
||||||
|
private readonly IGameSettingsService settingsService;
|
||||||
private readonly IGameStateService gameStateService;
|
private readonly IGameStateService gameStateService;
|
||||||
private readonly IMainMenuView view;
|
private readonly IMainMenuView view;
|
||||||
|
|
||||||
public MainMenuPresenter(IGameCommandDispatcher commandDispatcher, IGameStateService gameStateService, IMainMenuView view = null)
|
public MainMenuPresenter(IGameCommandDispatcher commandDispatcher, MinesweeperGameConfig config, IGameSettingsService settingsService, IGameStateService gameStateService, IMainMenuView view = null)
|
||||||
{
|
{
|
||||||
this.commandDispatcher = commandDispatcher;
|
this.commandDispatcher = commandDispatcher;
|
||||||
|
this.config = config;
|
||||||
|
this.settingsService = settingsService;
|
||||||
this.gameStateService = gameStateService;
|
this.gameStateService = gameStateService;
|
||||||
this.view = view;
|
this.view = view;
|
||||||
}
|
}
|
||||||
@@ -22,7 +27,9 @@ namespace Minesweeper.Presentation.Presenters
|
|||||||
if (view != null)
|
if (view != null)
|
||||||
{
|
{
|
||||||
view.StartClicked += OnStartClicked;
|
view.StartClicked += OnStartClicked;
|
||||||
|
view.SizeChanged += OnSizeChanged;
|
||||||
gameStateService.StateChanged += OnStateChanged;
|
gameStateService.StateChanged += OnStateChanged;
|
||||||
|
RefreshMenuValues(settingsService.Current);
|
||||||
OnStateChanged(gameStateService.Current);
|
OnStateChanged(gameStateService.Current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,19 +39,29 @@ namespace Minesweeper.Presentation.Presenters
|
|||||||
if (view != null)
|
if (view != null)
|
||||||
{
|
{
|
||||||
view.StartClicked -= OnStartClicked;
|
view.StartClicked -= OnStartClicked;
|
||||||
|
view.SizeChanged -= OnSizeChanged;
|
||||||
gameStateService.StateChanged -= OnStateChanged;
|
gameStateService.StateChanged -= OnStateChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStartClicked()
|
private void OnStartClicked()
|
||||||
{
|
{
|
||||||
|
settingsService.ApplyAndSaveIfChanged(view.SelectedSettings);
|
||||||
commandDispatcher.Dispatch(new StartGameCommand());
|
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)
|
private void OnStateChanged(GameState state)
|
||||||
{
|
{
|
||||||
if (state == GameState.FieldSelection)
|
if (state == GameState.FieldSelection)
|
||||||
{
|
{
|
||||||
|
RefreshMenuValues(settingsService.Current);
|
||||||
view.Show();
|
view.Show();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -52,5 +69,13 @@ namespace Minesweeper.Presentation.Presenters
|
|||||||
view.Hide();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Minesweeper.Config;
|
|
||||||
using Minesweeper.Core;
|
using Minesweeper.Core;
|
||||||
|
|
||||||
namespace Minesweeper.Presentation.ReadModels
|
namespace Minesweeper.Presentation.ReadModels
|
||||||
{
|
{
|
||||||
public sealed class GameReadModel : IGameReadModel
|
public sealed class GameReadModel : IGameReadModel
|
||||||
{
|
{
|
||||||
private readonly MinesweeperGameConfig config;
|
|
||||||
private readonly IBoardService boardService;
|
private readonly IBoardService boardService;
|
||||||
|
private readonly IGameSettingsService settingsService;
|
||||||
private readonly IGameStateService gameStateService;
|
private readonly IGameStateService gameStateService;
|
||||||
|
|
||||||
public GameReadModel(MinesweeperGameConfig config, IBoardService boardService, IGameStateService gameStateService)
|
public GameReadModel(IBoardService boardService, IGameSettingsService settingsService, IGameStateService gameStateService)
|
||||||
{
|
{
|
||||||
this.config = config;
|
|
||||||
this.boardService = boardService;
|
this.boardService = boardService;
|
||||||
|
this.settingsService = settingsService;
|
||||||
this.gameStateService = gameStateService;
|
this.gameStateService = gameStateService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GameState State => gameStateService.Current;
|
public GameState State => gameStateService.Current;
|
||||||
public int Width => boardService.Width > 0 ? boardService.Width : config.Width;
|
public int Width => boardService.Width > 0 ? boardService.Width : settingsService.SizeX;
|
||||||
public int Height => boardService.Height > 0 ? boardService.Height : config.Height;
|
public int Height => boardService.Height > 0 ? boardService.Height : settingsService.SizeY;
|
||||||
public int MinesCount => boardService.MinesCount > 0 ? boardService.MinesCount : config.MinesCount;
|
public int MinesCount => boardService.MinesCount > 0 ? boardService.MinesCount : settingsService.MinesCount;
|
||||||
public int FlaggedCellsCount => CountFlaggedCells();
|
public int FlaggedCellsCount => CountFlaggedCells();
|
||||||
public int RemainingMinesCount => MinesCount - FlaggedCellsCount;
|
public int RemainingMinesCount => MinesCount - FlaggedCellsCount;
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ namespace Minesweeper.Presentation.Views
|
|||||||
{
|
{
|
||||||
private const string ContentImagePath = "Content/Image";
|
private const string ContentImagePath = "Content/Image";
|
||||||
private const string ContentLabelPath = "Content/Text (TMP)";
|
private const string ContentLabelPath = "Content/Text (TMP)";
|
||||||
|
private const string ContentPath = "Content";
|
||||||
|
|
||||||
[SerializeField] private Button button;
|
[SerializeField] private Button button;
|
||||||
[SerializeField] private Image backgroundImage;
|
[SerializeField] private Image backgroundImage;
|
||||||
|
[SerializeField] private RectTransform contentRoot;
|
||||||
[SerializeField] private Image contentImage;
|
[SerializeField] private Image contentImage;
|
||||||
[SerializeField] private TMP_Text label;
|
[SerializeField] private TMP_Text label;
|
||||||
|
|
||||||
@@ -28,10 +30,11 @@ namespace Minesweeper.Presentation.Views
|
|||||||
public event Action PressStarted;
|
public event Action PressStarted;
|
||||||
public event Action PressEnded;
|
public event Action PressEnded;
|
||||||
|
|
||||||
public void Bind(Button button, Image backgroundImage, Image contentImage, TMP_Text label)
|
public void Bind(Button button, Image backgroundImage, RectTransform contentRoot, Image contentImage, TMP_Text label)
|
||||||
{
|
{
|
||||||
this.button = button;
|
this.button = button;
|
||||||
this.backgroundImage = backgroundImage;
|
this.backgroundImage = backgroundImage;
|
||||||
|
this.contentRoot = contentRoot;
|
||||||
this.contentImage = contentImage;
|
this.contentImage = contentImage;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
}
|
}
|
||||||
@@ -48,6 +51,15 @@ namespace Minesweeper.Presentation.Views
|
|||||||
backgroundImage = GetComponent<Image>();
|
backgroundImage = GetComponent<Image>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (contentRoot == null)
|
||||||
|
{
|
||||||
|
var contentTransform = transform.Find(ContentPath);
|
||||||
|
if (contentTransform != null)
|
||||||
|
{
|
||||||
|
contentRoot = contentTransform.GetComponent<RectTransform>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (contentImage == null)
|
if (contentImage == null)
|
||||||
{
|
{
|
||||||
var contentImageTransform = transform.Find(ContentImagePath);
|
var contentImageTransform = transform.Find(ContentImagePath);
|
||||||
@@ -82,12 +94,18 @@ namespace Minesweeper.Presentation.Views
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Render(BoardCellData cell, MinesweeperUiConfig config, float pixelsPerUnitMultiplier, bool revealUnflaggedMines)
|
public void Render(BoardCellData cell, MinesweeperUiConfig config, float pixelsPerUnitMultiplier, float contentPadding, bool revealUnflaggedMines)
|
||||||
{
|
{
|
||||||
gameObject.name = $"bt_{cell.X}_{cell.Y}_{cell.DisplayValue}";
|
gameObject.name = $"bt_{cell.X}_{cell.Y}_{cell.DisplayValue}";
|
||||||
isOpened = cell.IsOpened;
|
isOpened = cell.IsOpened;
|
||||||
var revealMine = revealUnflaggedMines && cell.IsMine && !cell.IsFlagged;
|
var revealMine = revealUnflaggedMines && cell.IsMine && !cell.IsFlagged;
|
||||||
|
|
||||||
|
if (contentRoot != null)
|
||||||
|
{
|
||||||
|
contentRoot.offsetMin = new Vector2(contentPadding, contentPadding);
|
||||||
|
contentRoot.offsetMax = new Vector2(-contentPadding, -contentPadding);
|
||||||
|
}
|
||||||
|
|
||||||
if (backgroundImage != null)
|
if (backgroundImage != null)
|
||||||
{
|
{
|
||||||
backgroundImage.pixelsPerUnitMultiplier = pixelsPerUnitMultiplier;
|
backgroundImage.pixelsPerUnitMultiplier = pixelsPerUnitMultiplier;
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ namespace Minesweeper.Presentation.Views
|
|||||||
{
|
{
|
||||||
private const float ResizeRefreshDelaySeconds = 0.5f;
|
private const float ResizeRefreshDelaySeconds = 0.5f;
|
||||||
private const float ResizeSizeEpsilon = 0.5f;
|
private const float ResizeSizeEpsilon = 0.5f;
|
||||||
|
private const float MaximumCellPixelsPerUnitMultiplier = 1f;
|
||||||
|
private const float ContentPaddingReferenceCellSize = 202f;
|
||||||
|
private const float ContentPaddingReferencePadding = 15f;
|
||||||
|
private const float MinimumContentPadding = 1f;
|
||||||
|
|
||||||
[SerializeField] private GameObject gameRoot;
|
[SerializeField] private GameObject gameRoot;
|
||||||
[SerializeField] private GameObject pauseRoot;
|
[SerializeField] private GameObject pauseRoot;
|
||||||
@@ -37,6 +41,7 @@ namespace Minesweeper.Presentation.Views
|
|||||||
private bool resizeRefreshPending;
|
private bool resizeRefreshPending;
|
||||||
private int currentBoardWidth;
|
private int currentBoardWidth;
|
||||||
private int currentBoardHeight;
|
private int currentBoardHeight;
|
||||||
|
private float currentContentPadding = MinimumContentPadding;
|
||||||
private float currentPixelsPerUnitMultiplier = 1f;
|
private float currentPixelsPerUnitMultiplier = 1f;
|
||||||
private float resizeStableAt;
|
private float resizeStableAt;
|
||||||
private Vector2 lastObservedLayoutSize;
|
private Vector2 lastObservedLayoutSize;
|
||||||
@@ -195,7 +200,7 @@ namespace Minesweeper.Presentation.Views
|
|||||||
var cell = cells[i];
|
var cell = cells[i];
|
||||||
if (cellsByCoordinate.TryGetValue(ToKey(cell.X, cell.Y), out var view))
|
if (cellsByCoordinate.TryGetValue(ToKey(cell.X, cell.Y), out var view))
|
||||||
{
|
{
|
||||||
view.Render(cell, uiConfig, currentPixelsPerUnitMultiplier, revealUnflaggedMines);
|
view.Render(cell, uiConfig, currentPixelsPerUnitMultiplier, currentContentPadding, revealUnflaggedMines);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,10 +353,16 @@ namespace Minesweeper.Presentation.Views
|
|||||||
gridLayoutGroup.padding = new RectOffset();
|
gridLayoutGroup.padding = new RectOffset();
|
||||||
gridLayoutGroup.spacing = new Vector2(spacing, spacing);
|
gridLayoutGroup.spacing = new Vector2(spacing, spacing);
|
||||||
gridLayoutGroup.cellSize = new Vector2(cellSize, cellSize);
|
gridLayoutGroup.cellSize = new Vector2(cellSize, cellSize);
|
||||||
currentPixelsPerUnitMultiplier = uiConfig.ReferenceCellSize / cellSize;
|
currentPixelsPerUnitMultiplier = Mathf.Min(MaximumCellPixelsPerUnitMultiplier, uiConfig.ReferenceCellSize / cellSize);
|
||||||
|
currentContentPadding = CalculateContentPadding(cellSize);
|
||||||
lastObservedLayoutSize = layoutSize;
|
lastObservedLayoutSize = layoutSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static float CalculateContentPadding(float cellSize)
|
||||||
|
{
|
||||||
|
return Mathf.Max(MinimumContentPadding, cellSize * ContentPaddingReferencePadding / ContentPaddingReferenceCellSize);
|
||||||
|
}
|
||||||
|
|
||||||
private Vector2 GetLayoutSourceSize()
|
private Vector2 GetLayoutSourceSize()
|
||||||
{
|
{
|
||||||
var parentRect = boardPanel.parent as RectTransform;
|
var parentRect = boardPanel.parent as RectTransform;
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Minesweeper.Core;
|
||||||
|
|
||||||
namespace Minesweeper.Presentation.Views
|
namespace Minesweeper.Presentation.Views
|
||||||
{
|
{
|
||||||
public interface IMainMenuView : IView
|
public interface IMainMenuView : IView
|
||||||
{
|
{
|
||||||
event Action StartClicked;
|
event Action StartClicked;
|
||||||
|
event Action SizeChanged;
|
||||||
|
|
||||||
|
GameSettingsValue SelectedSettings { get; }
|
||||||
|
|
||||||
void Show();
|
void Show();
|
||||||
void Hide();
|
void Hide();
|
||||||
|
void ConfigureSizeX(int min, int max, int value);
|
||||||
|
void ConfigureSizeY(int min, int max, int value);
|
||||||
|
void ConfigureMines(int min, int max, int value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Minesweeper.Core;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
@@ -8,8 +9,14 @@ namespace Minesweeper.Presentation.Views
|
|||||||
{
|
{
|
||||||
[SerializeField] private GameObject root;
|
[SerializeField] private GameObject root;
|
||||||
[SerializeField] private Button startButton;
|
[SerializeField] private Button startButton;
|
||||||
|
[SerializeField] private MenuSliderView sizeXSlider = new MenuSliderView();
|
||||||
|
[SerializeField] private MenuSliderView sizeYSlider = new MenuSliderView();
|
||||||
|
[SerializeField] private MenuSliderView minesSlider = new MenuSliderView();
|
||||||
|
|
||||||
public event Action StartClicked;
|
public event Action StartClicked;
|
||||||
|
public event Action SizeChanged;
|
||||||
|
|
||||||
|
public GameSettingsValue SelectedSettings => new GameSettingsValue(sizeXSlider.Value, sizeYSlider.Value, minesSlider.Value);
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
@@ -17,8 +24,6 @@ namespace Minesweeper.Presentation.Views
|
|||||||
{
|
{
|
||||||
root = gameObject;
|
root = gameObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoBind();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
@@ -27,6 +32,12 @@ namespace Minesweeper.Presentation.Views
|
|||||||
{
|
{
|
||||||
startButton.onClick.AddListener(OnStartClicked);
|
startButton.onClick.AddListener(OnStartClicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sizeXSlider.ValueChanged += OnSizeSliderChanged;
|
||||||
|
sizeYSlider.ValueChanged += OnSizeSliderChanged;
|
||||||
|
sizeXSlider.AddListeners();
|
||||||
|
sizeYSlider.AddListeners();
|
||||||
|
minesSlider.AddListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
@@ -35,6 +46,12 @@ namespace Minesweeper.Presentation.Views
|
|||||||
{
|
{
|
||||||
startButton.onClick.RemoveListener(OnStartClicked);
|
startButton.onClick.RemoveListener(OnStartClicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sizeXSlider.RemoveListeners();
|
||||||
|
sizeYSlider.RemoveListeners();
|
||||||
|
minesSlider.RemoveListeners();
|
||||||
|
sizeXSlider.ValueChanged -= OnSizeSliderChanged;
|
||||||
|
sizeYSlider.ValueChanged -= OnSizeSliderChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Show()
|
public void Show()
|
||||||
@@ -47,27 +64,34 @@ namespace Minesweeper.Presentation.Views
|
|||||||
root.SetActive(false);
|
root.SetActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ConfigureSizeX(int min, int max, int value)
|
||||||
|
{
|
||||||
|
sizeXSlider.Configure(min, max, value, "Size X");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureSizeY(int min, int max, int value)
|
||||||
|
{
|
||||||
|
sizeYSlider.Configure(min, max, value, "Size Y");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureMines(int min, int max, int value)
|
||||||
|
{
|
||||||
|
minesSlider.Configure(min, max, value, "Mines Count");
|
||||||
|
}
|
||||||
|
|
||||||
private void OnStartClicked()
|
private void OnStartClicked()
|
||||||
{
|
{
|
||||||
StartClicked?.Invoke();
|
StartClicked?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Bind(GameObject root, Button startButton)
|
public void BindRoot(GameObject root)
|
||||||
{
|
{
|
||||||
this.root = root != null ? root : gameObject;
|
this.root = root != null ? root : gameObject;
|
||||||
this.startButton = startButton;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AutoBind()
|
private void OnSizeSliderChanged(int value)
|
||||||
{
|
{
|
||||||
if (startButton == null)
|
SizeChanged?.Invoke();
|
||||||
{
|
|
||||||
var startButtonTransform = transform.Find("StartButton");
|
|
||||||
if (startButtonTransform != null)
|
|
||||||
{
|
|
||||||
startButton = startButtonTransform.GetComponent<Button>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace Minesweeper.Presentation.Views
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public sealed class MenuSliderView
|
||||||
|
{
|
||||||
|
[SerializeField] private Slider slider;
|
||||||
|
[SerializeField] private TMP_Text minText;
|
||||||
|
[SerializeField] private TMP_Text maxText;
|
||||||
|
[SerializeField] private TMP_Text valueText;
|
||||||
|
[SerializeField] private string valueLabel;
|
||||||
|
|
||||||
|
public event Action<int> ValueChanged;
|
||||||
|
|
||||||
|
public int Value => slider != null ? Mathf.RoundToInt(slider.value) : 0;
|
||||||
|
|
||||||
|
public void Bind(Slider slider, TMP_Text minText, TMP_Text maxText, TMP_Text valueText)
|
||||||
|
{
|
||||||
|
RemoveListeners();
|
||||||
|
this.slider = slider;
|
||||||
|
this.minText = minText;
|
||||||
|
this.maxText = maxText;
|
||||||
|
this.valueText = valueText;
|
||||||
|
AddListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddListeners()
|
||||||
|
{
|
||||||
|
if (slider != null)
|
||||||
|
{
|
||||||
|
slider.onValueChanged.AddListener(OnValueChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveListeners()
|
||||||
|
{
|
||||||
|
if (slider != null)
|
||||||
|
{
|
||||||
|
slider.onValueChanged.RemoveListener(OnValueChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(int min, int max, int value, string label)
|
||||||
|
{
|
||||||
|
if (slider == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
valueLabel = label;
|
||||||
|
var clampedMax = Mathf.Max(min, max);
|
||||||
|
var clampedValue = Mathf.Clamp(value, min, clampedMax);
|
||||||
|
slider.wholeNumbers = true;
|
||||||
|
slider.minValue = min;
|
||||||
|
slider.maxValue = clampedMax;
|
||||||
|
slider.SetValueWithoutNotify(clampedValue);
|
||||||
|
SetText(minText, min);
|
||||||
|
SetText(maxText, clampedMax);
|
||||||
|
SetValueText(clampedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnValueChanged(float value)
|
||||||
|
{
|
||||||
|
var intValue = Mathf.RoundToInt(value);
|
||||||
|
SetValueText(intValue);
|
||||||
|
ValueChanged?.Invoke(intValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetValueText(int value)
|
||||||
|
{
|
||||||
|
if (valueText != null)
|
||||||
|
{
|
||||||
|
valueText.text = string.IsNullOrEmpty(valueLabel) ? value.ToString() : $"{valueLabel}: {value}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetText(TMP_Text text, int value)
|
||||||
|
{
|
||||||
|
if (text != null)
|
||||||
|
{
|
||||||
|
text.text = value.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 15be53f58e067f944a33854111083046
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Minesweeper.Core;
|
||||||
|
|
||||||
namespace Minesweeper.Presentation.Views
|
namespace Minesweeper.Presentation.Views
|
||||||
{
|
{
|
||||||
@@ -10,6 +11,14 @@ namespace Minesweeper.Presentation.Views
|
|||||||
remove { }
|
remove { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event Action SizeChanged
|
||||||
|
{
|
||||||
|
add { }
|
||||||
|
remove { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameSettingsValue SelectedSettings => default;
|
||||||
|
|
||||||
public void Show()
|
public void Show()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -17,5 +26,17 @@ namespace Minesweeper.Presentation.Views
|
|||||||
public void Hide()
|
public void Hide()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ConfigureSizeX(int min, int max, int value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureSizeY(int min, int max, int value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureMines(int min, int max, int value)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user