From 1a6f403d72c9a81681317d633be3069572c3ea95 Mon Sep 17 00:00:00 2001 From: Konstantin Dyachenko Date: Sat, 6 Jun 2026 22:58:20 +0700 Subject: [PATCH] [Add] Resize board --- Assets/Config/MinesweeperGameConfig.asset | 2 +- .../Presentation/Presenters/GamePresenter.cs | 10 +- Assets/Runtime/Presentation/Views/CellView.cs | 18 ++-- Assets/Runtime/Presentation/Views/GameView.cs | 98 +++++++++++++++++-- .../Runtime/Presentation/Views/IGameView.cs | 4 +- .../Presentation/Views/NullGameView.cs | 4 +- Assets/Scenes/SampleScene.unity | 2 +- 7 files changed, 114 insertions(+), 24 deletions(-) diff --git a/Assets/Config/MinesweeperGameConfig.asset b/Assets/Config/MinesweeperGameConfig.asset index eb18a73..79f338f 100644 --- a/Assets/Config/MinesweeperGameConfig.asset +++ b/Assets/Config/MinesweeperGameConfig.asset @@ -14,5 +14,5 @@ MonoBehaviour: m_EditorClassIdentifier: Assembly-CSharp::Minesweeper.Config.MinesweeperGameConfig k__BackingField: 9 k__BackingField: 9 - k__BackingField: 10 + k__BackingField: 70 k__BackingField: 114 diff --git a/Assets/Runtime/Presentation/Presenters/GamePresenter.cs b/Assets/Runtime/Presentation/Presenters/GamePresenter.cs index c5d8435..400c12d 100644 --- a/Assets/Runtime/Presentation/Presenters/GamePresenter.cs +++ b/Assets/Runtime/Presentation/Presenters/GamePresenter.cs @@ -176,7 +176,7 @@ namespace Minesweeper.Presentation.Presenters { var cells = readModel.GetCells(); view.SetMineCount(readModel.MinesCount); - view.RebuildBoard(cells, readModel.Width, readModel.Height, cellViewFactory); + view.RebuildBoard(cells, readModel.Width, readModel.Height, cellViewFactory, IsFinalState()); boardBuilt = true; topPanelPresenter.RefreshCounters(); UpdateBoardInput(); @@ -184,7 +184,13 @@ namespace Minesweeper.Presentation.Presenters private void RefreshBoard() { - view.RefreshBoard(readModel.GetCells()); + view.RefreshBoard(readModel.GetCells(), IsFinalState()); + } + + private bool IsFinalState() + { + var state = gameStateService.Current; + return state == GameState.Lost || state == GameState.Won; } private void UpdateBoardInput() diff --git a/Assets/Runtime/Presentation/Views/CellView.cs b/Assets/Runtime/Presentation/Views/CellView.cs index 08ce8e9..c1a41a9 100644 --- a/Assets/Runtime/Presentation/Views/CellView.cs +++ b/Assets/Runtime/Presentation/Views/CellView.cs @@ -82,15 +82,16 @@ namespace Minesweeper.Presentation.Views } } - public void Render(BoardCellData cell, MinesweeperUiConfig config, float pixelsPerUnitMultiplier) + public void Render(BoardCellData cell, MinesweeperUiConfig config, float pixelsPerUnitMultiplier, bool revealUnflaggedMines) { gameObject.name = $"bt_{cell.X}_{cell.Y}_{cell.DisplayValue}"; isOpened = cell.IsOpened; + var revealMine = revealUnflaggedMines && cell.IsMine && !cell.IsFlagged; if (backgroundImage != null) { backgroundImage.pixelsPerUnitMultiplier = pixelsPerUnitMultiplier; - backgroundImage.color = cell.IsOpened ? config.OpenedCellColor : config.ClosedCellColor; + backgroundImage.color = cell.IsOpened || revealMine ? config.OpenedCellColor : config.ClosedCellColor; } if (contentImage != null) @@ -98,7 +99,7 @@ namespace Minesweeper.Presentation.Views contentImage.pixelsPerUnitMultiplier = pixelsPerUnitMultiplier; } - RenderContent(cell, config); + RenderContent(cell, config, revealMine); if (button != null) { @@ -106,15 +107,16 @@ namespace Minesweeper.Presentation.Views } } - private void RenderContent(BoardCellData cell, MinesweeperUiConfig config) + private void RenderContent(BoardCellData cell, MinesweeperUiConfig config, bool revealMine) { var showFlag = cell.IsFlagged && !cell.IsOpened; - var showMine = cell.IsOpened && cell.IsMine; + var showMine = (cell.IsOpened && cell.IsMine) || revealMine; + var showMineAsText = showMine && config.MineSprite == null; var showNumber = cell.IsOpened && !cell.IsMine && cell.NeighborMines > 0; if (contentImage != null) { - contentImage.gameObject.SetActive(showFlag || showMine); + contentImage.gameObject.SetActive(showFlag || (showMine && !showMineAsText)); if (showFlag) { contentImage.sprite = config.FlagSprite; @@ -129,8 +131,8 @@ namespace Minesweeper.Presentation.Views if (label != null) { - label.gameObject.SetActive(showNumber); - label.text = showNumber ? cell.NeighborMines.ToString() : string.Empty; + label.gameObject.SetActive(showNumber || showMineAsText); + label.text = showMineAsText ? "M" : showNumber ? cell.NeighborMines.ToString() : string.Empty; label.color = config.GetNumberTextColor(cell.NeighborMines); } } diff --git a/Assets/Runtime/Presentation/Views/GameView.cs b/Assets/Runtime/Presentation/Views/GameView.cs index 0c016c5..a3b21d9 100644 --- a/Assets/Runtime/Presentation/Views/GameView.cs +++ b/Assets/Runtime/Presentation/Views/GameView.cs @@ -11,6 +11,9 @@ namespace Minesweeper.Presentation.Views { public sealed class GameView : MonoBehaviour, IGameView { + private const float ResizeRefreshDelaySeconds = 0.5f; + private const float ResizeSizeEpsilon = 0.5f; + [SerializeField] private GameObject gameRoot; [SerializeField] private GameObject pauseRoot; [SerializeField] private GameObject resultRoot; @@ -28,8 +31,15 @@ namespace Minesweeper.Presentation.Views [SerializeField] private MinesweeperUiConfig uiConfig; private readonly Dictionary cellsByCoordinate = new Dictionary(); + private IReadOnlyList currentCells; private bool boardInputEnabled = true; + private bool currentRevealUnflaggedMines; + private bool resizeRefreshPending; + private int currentBoardWidth; + private int currentBoardHeight; private float currentPixelsPerUnitMultiplier = 1f; + private float resizeStableAt; + private Vector2 lastObservedLayoutSize; public event Action RestartRequested; public event Action GoToMenuRequested; @@ -58,6 +68,11 @@ namespace Minesweeper.Presentation.Views RemoveButtonListeners(); } + private void Update() + { + TrackBoardResize(); + } + public void ShowGame() { gameRoot.SetActive(true); @@ -128,9 +143,13 @@ namespace Minesweeper.Presentation.Views uiConfig = config; } - public void RebuildBoard(IReadOnlyList cells, int width, int height, ICellViewFactory cellViewFactory) + public void RebuildBoard(IReadOnlyList cells, int width, int height, ICellViewFactory cellViewFactory, bool revealUnflaggedMines) { ClearBoard(); + currentBoardWidth = width; + currentBoardHeight = height; + currentCells = cells; + currentRevealUnflaggedMines = revealUnflaggedMines; ConfigureGrid(width, height); for (var i = 0; i < cells.Count; i++) @@ -138,17 +157,20 @@ namespace Minesweeper.Presentation.Views CreateCell(cells[i], cellViewFactory); } - RefreshBoard(cells); + RefreshBoard(cells, revealUnflaggedMines); } - public void RefreshBoard(IReadOnlyList cells) + public void RefreshBoard(IReadOnlyList cells, bool revealUnflaggedMines) { + currentCells = cells; + currentRevealUnflaggedMines = revealUnflaggedMines; + for (var i = 0; i < cells.Count; i++) { var cell = cells[i]; if (cellsByCoordinate.TryGetValue(ToKey(cell.X, cell.Y), out var view)) { - view.Render(cell, uiConfig, currentPixelsPerUnitMultiplier); + view.Render(cell, uiConfig, currentPixelsPerUnitMultiplier, revealUnflaggedMines); } } } @@ -234,6 +256,45 @@ namespace Minesweeper.Presentation.Views { boardPanel.gameObject.SetActive(active); } + + if (active) + { + ResetResizeTracking(); + } + } + + private void TrackBoardResize() + { + if (boardPanel == null || !boardPanel.gameObject.activeInHierarchy || currentCells == null || currentBoardWidth <= 0 || currentBoardHeight <= 0) + { + return; + } + + var layoutSize = GetLayoutSourceSize(); + if (layoutSize.x <= 0f || layoutSize.y <= 0f) + { + return; + } + + if (HasSizeChanged(layoutSize, lastObservedLayoutSize)) + { + lastObservedLayoutSize = layoutSize; + resizeStableAt = Time.unscaledTime + ResizeRefreshDelaySeconds; + resizeRefreshPending = true; + return; + } + + if (resizeRefreshPending && Time.unscaledTime >= resizeStableAt) + { + resizeRefreshPending = false; + RefreshBoardLayout(); + } + } + + private void RefreshBoardLayout() + { + ConfigureGrid(currentBoardWidth, currentBoardHeight); + RefreshBoard(currentCells, currentRevealUnflaggedMines); } private void ConfigureGrid(int width, int height) @@ -245,10 +306,9 @@ namespace Minesweeper.Presentation.Views Canvas.ForceUpdateCanvases(); - var parentRect = boardPanel.parent as RectTransform; - var rect = parentRect != null ? parentRect.rect : boardPanel.rect; - var panelWidth = rect.width > 0f ? rect.width : uiConfig.ReferenceCellSize * width; - var panelHeight = rect.height > 0f ? rect.height : uiConfig.ReferenceCellSize * height; + var layoutSize = GetLayoutSourceSize(); + var panelWidth = layoutSize.x > 0f ? layoutSize.x : uiConfig.ReferenceCellSize * width; + var panelHeight = layoutSize.y > 0f ? layoutSize.y : uiConfig.ReferenceCellSize * height; var cellSize = CalculateCellSize(panelWidth, panelHeight, width, height); var padding = cellSize * uiConfig.BoardPaddingRatio; var spacing = cellSize * uiConfig.GridSpacingRatio; @@ -264,6 +324,28 @@ namespace Minesweeper.Presentation.Views gridLayoutGroup.spacing = new Vector2(spacing, spacing); gridLayoutGroup.cellSize = new Vector2(cellSize, cellSize); currentPixelsPerUnitMultiplier = uiConfig.ReferenceCellSize / cellSize; + lastObservedLayoutSize = layoutSize; + } + + private Vector2 GetLayoutSourceSize() + { + var parentRect = boardPanel.parent as RectTransform; + var rect = parentRect != null ? parentRect.rect : boardPanel.rect; + return rect.size; + } + + private void ResetResizeTracking() + { + resizeRefreshPending = false; + if (boardPanel != null) + { + lastObservedLayoutSize = GetLayoutSourceSize(); + } + } + + private static bool HasSizeChanged(Vector2 current, Vector2 previous) + { + return Mathf.Abs(current.x - previous.x) > ResizeSizeEpsilon || Mathf.Abs(current.y - previous.y) > ResizeSizeEpsilon; } private float CalculateCellSize(float panelWidth, float panelHeight, int width, int height) diff --git a/Assets/Runtime/Presentation/Views/IGameView.cs b/Assets/Runtime/Presentation/Views/IGameView.cs index cbc9dd8..45ef594 100644 --- a/Assets/Runtime/Presentation/Views/IGameView.cs +++ b/Assets/Runtime/Presentation/Views/IGameView.cs @@ -24,8 +24,8 @@ namespace Minesweeper.Presentation.Views void HideResult(); void SetMineCount(int minesCount); void SetTimer(float seconds); - void RebuildBoard(IReadOnlyList cells, int width, int height, ICellViewFactory cellViewFactory); - void RefreshBoard(IReadOnlyList cells); + void RebuildBoard(IReadOnlyList cells, int width, int height, ICellViewFactory cellViewFactory, bool revealUnflaggedMines); + void RefreshBoard(IReadOnlyList cells, bool revealUnflaggedMines); void SetBoardInputEnabled(bool enabled); } } diff --git a/Assets/Runtime/Presentation/Views/NullGameView.cs b/Assets/Runtime/Presentation/Views/NullGameView.cs index 496c657..195fd6c 100644 --- a/Assets/Runtime/Presentation/Views/NullGameView.cs +++ b/Assets/Runtime/Presentation/Views/NullGameView.cs @@ -87,11 +87,11 @@ namespace Minesweeper.Presentation.Views { } - public void RebuildBoard(IReadOnlyList cells, int width, int height, ICellViewFactory cellViewFactory) + public void RebuildBoard(IReadOnlyList cells, int width, int height, ICellViewFactory cellViewFactory, bool revealUnflaggedMines) { } - public void RefreshBoard(IReadOnlyList cells) + public void RefreshBoard(IReadOnlyList cells, bool revealUnflaggedMines) { } diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index a18d77b..62a0e44 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -2820,8 +2820,8 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1088026588} - - {fileID: 628970845} - {fileID: 1183237467} + - {fileID: 628970845} - {fileID: 1089412747} m_Father: {fileID: 289057769} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}