[Add] Resize board

This commit is contained in:
2026-06-06 22:58:20 +07:00
parent bb2463d970
commit 1a6f403d72
7 changed files with 114 additions and 24 deletions
+1 -1
View File
@@ -14,5 +14,5 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::Minesweeper.Config.MinesweeperGameConfig m_EditorClassIdentifier: Assembly-CSharp::Minesweeper.Config.MinesweeperGameConfig
<Width>k__BackingField: 9 <Width>k__BackingField: 9
<Height>k__BackingField: 9 <Height>k__BackingField: 9
<MinesCount>k__BackingField: 10 <MinesCount>k__BackingField: 70
<RestartKey>k__BackingField: 114 <RestartKey>k__BackingField: 114
@@ -176,7 +176,7 @@ namespace Minesweeper.Presentation.Presenters
{ {
var cells = readModel.GetCells(); var cells = readModel.GetCells();
view.SetMineCount(readModel.MinesCount); view.SetMineCount(readModel.MinesCount);
view.RebuildBoard(cells, readModel.Width, readModel.Height, cellViewFactory); view.RebuildBoard(cells, readModel.Width, readModel.Height, cellViewFactory, IsFinalState());
boardBuilt = true; boardBuilt = true;
topPanelPresenter.RefreshCounters(); topPanelPresenter.RefreshCounters();
UpdateBoardInput(); UpdateBoardInput();
@@ -184,7 +184,13 @@ namespace Minesweeper.Presentation.Presenters
private void RefreshBoard() 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() private void UpdateBoardInput()
+10 -8
View File
@@ -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}"; gameObject.name = $"bt_{cell.X}_{cell.Y}_{cell.DisplayValue}";
isOpened = cell.IsOpened; isOpened = cell.IsOpened;
var revealMine = revealUnflaggedMines && cell.IsMine && !cell.IsFlagged;
if (backgroundImage != null) if (backgroundImage != null)
{ {
backgroundImage.pixelsPerUnitMultiplier = pixelsPerUnitMultiplier; backgroundImage.pixelsPerUnitMultiplier = pixelsPerUnitMultiplier;
backgroundImage.color = cell.IsOpened ? config.OpenedCellColor : config.ClosedCellColor; backgroundImage.color = cell.IsOpened || revealMine ? config.OpenedCellColor : config.ClosedCellColor;
} }
if (contentImage != null) if (contentImage != null)
@@ -98,7 +99,7 @@ namespace Minesweeper.Presentation.Views
contentImage.pixelsPerUnitMultiplier = pixelsPerUnitMultiplier; contentImage.pixelsPerUnitMultiplier = pixelsPerUnitMultiplier;
} }
RenderContent(cell, config); RenderContent(cell, config, revealMine);
if (button != null) 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 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; var showNumber = cell.IsOpened && !cell.IsMine && cell.NeighborMines > 0;
if (contentImage != null) if (contentImage != null)
{ {
contentImage.gameObject.SetActive(showFlag || showMine); contentImage.gameObject.SetActive(showFlag || (showMine && !showMineAsText));
if (showFlag) if (showFlag)
{ {
contentImage.sprite = config.FlagSprite; contentImage.sprite = config.FlagSprite;
@@ -129,8 +131,8 @@ namespace Minesweeper.Presentation.Views
if (label != null) if (label != null)
{ {
label.gameObject.SetActive(showNumber); label.gameObject.SetActive(showNumber || showMineAsText);
label.text = showNumber ? cell.NeighborMines.ToString() : string.Empty; label.text = showMineAsText ? "M" : showNumber ? cell.NeighborMines.ToString() : string.Empty;
label.color = config.GetNumberTextColor(cell.NeighborMines); label.color = config.GetNumberTextColor(cell.NeighborMines);
} }
} }
+90 -8
View File
@@ -11,6 +11,9 @@ namespace Minesweeper.Presentation.Views
{ {
public sealed class GameView : MonoBehaviour, IGameView 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 gameRoot;
[SerializeField] private GameObject pauseRoot; [SerializeField] private GameObject pauseRoot;
[SerializeField] private GameObject resultRoot; [SerializeField] private GameObject resultRoot;
@@ -28,8 +31,15 @@ namespace Minesweeper.Presentation.Views
[SerializeField] private MinesweeperUiConfig uiConfig; [SerializeField] private MinesweeperUiConfig uiConfig;
private readonly Dictionary<int, CellView> cellsByCoordinate = new Dictionary<int, CellView>(); private readonly Dictionary<int, CellView> cellsByCoordinate = new Dictionary<int, CellView>();
private IReadOnlyList<BoardCellData> currentCells;
private bool boardInputEnabled = true; private bool boardInputEnabled = true;
private bool currentRevealUnflaggedMines;
private bool resizeRefreshPending;
private int currentBoardWidth;
private int currentBoardHeight;
private float currentPixelsPerUnitMultiplier = 1f; private float currentPixelsPerUnitMultiplier = 1f;
private float resizeStableAt;
private Vector2 lastObservedLayoutSize;
public event Action RestartRequested; public event Action RestartRequested;
public event Action GoToMenuRequested; public event Action GoToMenuRequested;
@@ -58,6 +68,11 @@ namespace Minesweeper.Presentation.Views
RemoveButtonListeners(); RemoveButtonListeners();
} }
private void Update()
{
TrackBoardResize();
}
public void ShowGame() public void ShowGame()
{ {
gameRoot.SetActive(true); gameRoot.SetActive(true);
@@ -128,9 +143,13 @@ namespace Minesweeper.Presentation.Views
uiConfig = config; uiConfig = config;
} }
public void RebuildBoard(IReadOnlyList<BoardCellData> cells, int width, int height, ICellViewFactory cellViewFactory) public void RebuildBoard(IReadOnlyList<BoardCellData> cells, int width, int height, ICellViewFactory cellViewFactory, bool revealUnflaggedMines)
{ {
ClearBoard(); ClearBoard();
currentBoardWidth = width;
currentBoardHeight = height;
currentCells = cells;
currentRevealUnflaggedMines = revealUnflaggedMines;
ConfigureGrid(width, height); ConfigureGrid(width, height);
for (var i = 0; i < cells.Count; i++) for (var i = 0; i < cells.Count; i++)
@@ -138,17 +157,20 @@ namespace Minesweeper.Presentation.Views
CreateCell(cells[i], cellViewFactory); CreateCell(cells[i], cellViewFactory);
} }
RefreshBoard(cells); RefreshBoard(cells, revealUnflaggedMines);
} }
public void RefreshBoard(IReadOnlyList<BoardCellData> cells) public void RefreshBoard(IReadOnlyList<BoardCellData> cells, bool revealUnflaggedMines)
{ {
currentCells = cells;
currentRevealUnflaggedMines = revealUnflaggedMines;
for (var i = 0; i < cells.Count; i++) for (var i = 0; i < cells.Count; i++)
{ {
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); view.Render(cell, uiConfig, currentPixelsPerUnitMultiplier, revealUnflaggedMines);
} }
} }
} }
@@ -234,6 +256,45 @@ namespace Minesweeper.Presentation.Views
{ {
boardPanel.gameObject.SetActive(active); 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) private void ConfigureGrid(int width, int height)
@@ -245,10 +306,9 @@ namespace Minesweeper.Presentation.Views
Canvas.ForceUpdateCanvases(); Canvas.ForceUpdateCanvases();
var parentRect = boardPanel.parent as RectTransform; var layoutSize = GetLayoutSourceSize();
var rect = parentRect != null ? parentRect.rect : boardPanel.rect; var panelWidth = layoutSize.x > 0f ? layoutSize.x : uiConfig.ReferenceCellSize * width;
var panelWidth = rect.width > 0f ? rect.width : uiConfig.ReferenceCellSize * width; var panelHeight = layoutSize.y > 0f ? layoutSize.y : uiConfig.ReferenceCellSize * height;
var panelHeight = rect.height > 0f ? rect.height : uiConfig.ReferenceCellSize * height;
var cellSize = CalculateCellSize(panelWidth, panelHeight, width, height); var cellSize = CalculateCellSize(panelWidth, panelHeight, width, height);
var padding = cellSize * uiConfig.BoardPaddingRatio; var padding = cellSize * uiConfig.BoardPaddingRatio;
var spacing = cellSize * uiConfig.GridSpacingRatio; var spacing = cellSize * uiConfig.GridSpacingRatio;
@@ -264,6 +324,28 @@ namespace Minesweeper.Presentation.Views
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 = 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) private float CalculateCellSize(float panelWidth, float panelHeight, int width, int height)
@@ -24,8 +24,8 @@ namespace Minesweeper.Presentation.Views
void HideResult(); void HideResult();
void SetMineCount(int minesCount); void SetMineCount(int minesCount);
void SetTimer(float seconds); void SetTimer(float seconds);
void RebuildBoard(IReadOnlyList<BoardCellData> cells, int width, int height, ICellViewFactory cellViewFactory); void RebuildBoard(IReadOnlyList<BoardCellData> cells, int width, int height, ICellViewFactory cellViewFactory, bool revealUnflaggedMines);
void RefreshBoard(IReadOnlyList<BoardCellData> cells); void RefreshBoard(IReadOnlyList<BoardCellData> cells, bool revealUnflaggedMines);
void SetBoardInputEnabled(bool enabled); void SetBoardInputEnabled(bool enabled);
} }
} }
@@ -87,11 +87,11 @@ namespace Minesweeper.Presentation.Views
{ {
} }
public void RebuildBoard(IReadOnlyList<BoardCellData> cells, int width, int height, ICellViewFactory cellViewFactory) public void RebuildBoard(IReadOnlyList<BoardCellData> cells, int width, int height, ICellViewFactory cellViewFactory, bool revealUnflaggedMines)
{ {
} }
public void RefreshBoard(IReadOnlyList<BoardCellData> cells) public void RefreshBoard(IReadOnlyList<BoardCellData> cells, bool revealUnflaggedMines)
{ {
} }
+1 -1
View File
@@ -2820,8 +2820,8 @@ RectTransform:
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: m_Children:
- {fileID: 1088026588} - {fileID: 1088026588}
- {fileID: 628970845}
- {fileID: 1183237467} - {fileID: 1183237467}
- {fileID: 628970845}
- {fileID: 1089412747} - {fileID: 1089412747}
m_Father: {fileID: 289057769} m_Father: {fileID: 289057769}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}