[Add] UI, menu, pause and timer
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using Minesweeper.Core;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Minesweeper.Presentation.Views
|
||||
{
|
||||
public sealed class CellView : MonoBehaviour, IPointerClickHandler
|
||||
{
|
||||
[SerializeField] private Button button;
|
||||
[SerializeField] private Image image;
|
||||
[SerializeField] private TMP_Text label;
|
||||
|
||||
private int x;
|
||||
private int y;
|
||||
private bool inputEnabled = true;
|
||||
|
||||
public event Action<int, int> OpenRequested;
|
||||
public event Action<int, int> FlagRequested;
|
||||
|
||||
public void Bind(Button button, Image image, TMP_Text label)
|
||||
{
|
||||
this.button = button;
|
||||
this.image = image;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public void Initialize(int x, int y)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public void SetInputEnabled(bool enabled)
|
||||
{
|
||||
inputEnabled = enabled;
|
||||
if (button != null)
|
||||
{
|
||||
button.interactable = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(BoardCellData cell, float pixelsPerUnitMultiplier)
|
||||
{
|
||||
gameObject.name = $"bt_{cell.X}_{cell.Y}_{cell.DisplayValue}";
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
image.pixelsPerUnitMultiplier = pixelsPerUnitMultiplier;
|
||||
image.color = cell.IsOpened ? new Color(0.78f, 0.78f, 0.78f) : Color.white;
|
||||
}
|
||||
|
||||
if (label != null)
|
||||
{
|
||||
if (cell.IsFlagged)
|
||||
{
|
||||
label.text = "F";
|
||||
}
|
||||
else if (!cell.IsOpened)
|
||||
{
|
||||
label.text = string.Empty;
|
||||
}
|
||||
else if (cell.IsMine)
|
||||
{
|
||||
label.text = "M";
|
||||
}
|
||||
else
|
||||
{
|
||||
label.text = cell.NeighborMines == 0 ? string.Empty : cell.NeighborMines.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
if (!inputEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventData.button == PointerEventData.InputButton.Left)
|
||||
{
|
||||
OpenRequested?.Invoke(x, y);
|
||||
}
|
||||
else if (eventData.button == PointerEventData.InputButton.Right)
|
||||
{
|
||||
FlagRequested?.Invoke(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2904d462d22809c499afe1842f6e6239
|
||||
@@ -0,0 +1,289 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Minesweeper.Core;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Minesweeper.Presentation.Views
|
||||
{
|
||||
public sealed class GameView : MonoBehaviour, IGameView
|
||||
{
|
||||
[SerializeField] private GameObject gameRoot;
|
||||
[SerializeField] private GameObject pauseRoot;
|
||||
[SerializeField] private RectTransform boardPanel;
|
||||
[SerializeField] private GridLayoutGroup gridLayoutGroup;
|
||||
[SerializeField] private Button pauseButton;
|
||||
[SerializeField] private Button restartButton;
|
||||
[SerializeField] private Button resumeButton;
|
||||
[SerializeField] private Button mainMenuButton;
|
||||
[SerializeField] private TMP_Text timerText;
|
||||
[SerializeField] private TMP_Text mineText;
|
||||
[SerializeField] private float spacing = 2f;
|
||||
[SerializeField] private float basePixelsPerUnitCellSize = 32f;
|
||||
|
||||
private readonly Dictionary<int, CellView> cellsByCoordinate = new Dictionary<int, CellView>();
|
||||
private bool boardInputEnabled = true;
|
||||
private float currentPixelsPerUnitMultiplier = 1f;
|
||||
|
||||
public event Action RestartRequested;
|
||||
public event Action GoToMenuRequested;
|
||||
public event Action PauseRequested;
|
||||
public event Action ResumeRequested;
|
||||
public event Action<int, int> CellOpenRequested;
|
||||
public event Action<int, int> CellFlagRequested;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (gameRoot == null)
|
||||
{
|
||||
gameRoot = gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
AddButtonListeners();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
RemoveButtonListeners();
|
||||
}
|
||||
|
||||
public void ShowGame()
|
||||
{
|
||||
gameRoot.SetActive(true);
|
||||
}
|
||||
|
||||
public void HideGame()
|
||||
{
|
||||
gameRoot.SetActive(false);
|
||||
}
|
||||
|
||||
public void ShowPause()
|
||||
{
|
||||
if (pauseRoot != null)
|
||||
{
|
||||
pauseRoot.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void HidePause()
|
||||
{
|
||||
if (pauseRoot != null)
|
||||
{
|
||||
pauseRoot.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTimer(float seconds)
|
||||
{
|
||||
if (timerText != null)
|
||||
{
|
||||
timerText.text = Mathf.FloorToInt(seconds).ToString("000");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMineCount(int minesCount)
|
||||
{
|
||||
if (mineText != null)
|
||||
{
|
||||
mineText.text = minesCount.ToString("000");
|
||||
}
|
||||
}
|
||||
|
||||
public void RebuildBoard(IReadOnlyList<BoardCellData> cells, int width, int height)
|
||||
{
|
||||
ClearBoard();
|
||||
ConfigureGrid(width, height);
|
||||
|
||||
for (var i = 0; i < cells.Count; i++)
|
||||
{
|
||||
CreateCell(cells[i]);
|
||||
}
|
||||
|
||||
RefreshBoard(cells);
|
||||
}
|
||||
|
||||
public void RefreshBoard(IReadOnlyList<BoardCellData> cells)
|
||||
{
|
||||
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, currentPixelsPerUnitMultiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetBoardInputEnabled(bool enabled)
|
||||
{
|
||||
boardInputEnabled = enabled;
|
||||
foreach (var cell in cellsByCoordinate.Values)
|
||||
{
|
||||
cell.SetInputEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddButtonListeners()
|
||||
{
|
||||
if (pauseButton != null)
|
||||
{
|
||||
pauseButton.onClick.AddListener(OnPauseClicked);
|
||||
}
|
||||
|
||||
if (restartButton != null)
|
||||
{
|
||||
restartButton.onClick.AddListener(OnRestartClicked);
|
||||
}
|
||||
|
||||
if (resumeButton != null)
|
||||
{
|
||||
resumeButton.onClick.AddListener(OnResumeClicked);
|
||||
}
|
||||
|
||||
if (mainMenuButton != null)
|
||||
{
|
||||
mainMenuButton.onClick.AddListener(OnMainMenuClicked);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveButtonListeners()
|
||||
{
|
||||
if (pauseButton != null)
|
||||
{
|
||||
pauseButton.onClick.RemoveListener(OnPauseClicked);
|
||||
}
|
||||
|
||||
if (restartButton != null)
|
||||
{
|
||||
restartButton.onClick.RemoveListener(OnRestartClicked);
|
||||
}
|
||||
|
||||
if (resumeButton != null)
|
||||
{
|
||||
resumeButton.onClick.RemoveListener(OnResumeClicked);
|
||||
}
|
||||
|
||||
if (mainMenuButton != null)
|
||||
{
|
||||
mainMenuButton.onClick.RemoveListener(OnMainMenuClicked);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureGrid(int width, int height)
|
||||
{
|
||||
if (gridLayoutGroup == null || boardPanel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Canvas.ForceUpdateCanvases();
|
||||
|
||||
var rect = boardPanel.rect;
|
||||
var panelWidth = rect.width > 0f ? rect.width : 512f;
|
||||
var panelHeight = rect.height > 0f ? rect.height : 512f;
|
||||
var padding = gridLayoutGroup.padding;
|
||||
var availableWidth = panelWidth - padding.left - padding.right - spacing * Mathf.Max(0, width - 1);
|
||||
var availableHeight = panelHeight - padding.top - padding.bottom - spacing * Mathf.Max(0, height - 1);
|
||||
var cellSize = Mathf.Floor(Mathf.Min(availableWidth / width, availableHeight / height));
|
||||
cellSize = Mathf.Max(8f, cellSize);
|
||||
|
||||
gridLayoutGroup.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
|
||||
gridLayoutGroup.constraintCount = width;
|
||||
gridLayoutGroup.spacing = new Vector2(spacing, spacing);
|
||||
gridLayoutGroup.cellSize = new Vector2(cellSize, cellSize);
|
||||
currentPixelsPerUnitMultiplier = Mathf.Clamp(basePixelsPerUnitCellSize / cellSize, 0.25f, 4f);
|
||||
}
|
||||
|
||||
private void CreateCell(BoardCellData cell)
|
||||
{
|
||||
var go = new GameObject($"bt_{cell.X}_{cell.Y}_{cell.DisplayValue}", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button));
|
||||
go.transform.SetParent(gridLayoutGroup.transform, false);
|
||||
|
||||
var labelGo = new GameObject("Text", typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI));
|
||||
labelGo.transform.SetParent(go.transform, false);
|
||||
var labelRect = (RectTransform)labelGo.transform;
|
||||
labelRect.anchorMin = Vector2.zero;
|
||||
labelRect.anchorMax = Vector2.one;
|
||||
labelRect.offsetMin = Vector2.zero;
|
||||
labelRect.offsetMax = Vector2.zero;
|
||||
|
||||
var label = labelGo.GetComponent<TextMeshProUGUI>();
|
||||
label.alignment = TextAlignmentOptions.Center;
|
||||
label.enableAutoSizing = true;
|
||||
label.fontSizeMin = 6f;
|
||||
label.fontSizeMax = 32f;
|
||||
label.color = Color.black;
|
||||
|
||||
var view = go.AddComponent<CellView>();
|
||||
view.Bind(go.GetComponent<Button>(), go.GetComponent<Image>(), label);
|
||||
view.Initialize(cell.X, cell.Y);
|
||||
view.SetInputEnabled(boardInputEnabled);
|
||||
view.OpenRequested += OnCellOpenRequested;
|
||||
view.FlagRequested += OnCellFlagRequested;
|
||||
cellsByCoordinate[ToKey(cell.X, cell.Y)] = view;
|
||||
}
|
||||
|
||||
private void ClearBoard()
|
||||
{
|
||||
foreach (var cell in cellsByCoordinate.Values)
|
||||
{
|
||||
if (cell != null)
|
||||
{
|
||||
cell.OpenRequested -= OnCellOpenRequested;
|
||||
cell.FlagRequested -= OnCellFlagRequested;
|
||||
}
|
||||
}
|
||||
|
||||
cellsByCoordinate.Clear();
|
||||
|
||||
if (gridLayoutGroup == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = gridLayoutGroup.transform.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
Destroy(gridLayoutGroup.transform.GetChild(i).gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCellOpenRequested(int x, int y)
|
||||
{
|
||||
CellOpenRequested?.Invoke(x, y);
|
||||
}
|
||||
|
||||
private void OnCellFlagRequested(int x, int y)
|
||||
{
|
||||
CellFlagRequested?.Invoke(x, y);
|
||||
}
|
||||
|
||||
private void OnPauseClicked()
|
||||
{
|
||||
PauseRequested?.Invoke();
|
||||
}
|
||||
|
||||
private void OnRestartClicked()
|
||||
{
|
||||
RestartRequested?.Invoke();
|
||||
}
|
||||
|
||||
private void OnResumeClicked()
|
||||
{
|
||||
ResumeRequested?.Invoke();
|
||||
}
|
||||
|
||||
private void OnMainMenuClicked()
|
||||
{
|
||||
GoToMenuRequested?.Invoke();
|
||||
}
|
||||
|
||||
private static int ToKey(int x, int y)
|
||||
{
|
||||
return (y << 16) ^ x;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c906a10872edd04480e534703fc4fea
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Minesweeper.Core;
|
||||
|
||||
namespace Minesweeper.Presentation.Views
|
||||
{
|
||||
@@ -6,5 +8,19 @@ namespace Minesweeper.Presentation.Views
|
||||
{
|
||||
event Action RestartRequested;
|
||||
event Action GoToMenuRequested;
|
||||
event Action PauseRequested;
|
||||
event Action ResumeRequested;
|
||||
event Action<int, int> CellOpenRequested;
|
||||
event Action<int, int> CellFlagRequested;
|
||||
|
||||
void ShowGame();
|
||||
void HideGame();
|
||||
void ShowPause();
|
||||
void HidePause();
|
||||
void SetMineCount(int minesCount);
|
||||
void SetTimer(float seconds);
|
||||
void RebuildBoard(IReadOnlyList<BoardCellData> cells, int width, int height);
|
||||
void RefreshBoard(IReadOnlyList<BoardCellData> cells);
|
||||
void SetBoardInputEnabled(bool enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,8 @@ namespace Minesweeper.Presentation.Views
|
||||
public interface IMainMenuView : IView
|
||||
{
|
||||
event Action StartClicked;
|
||||
|
||||
void Show();
|
||||
void Hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Minesweeper.Presentation.Views
|
||||
{
|
||||
public sealed class MainMenuView : MonoBehaviour, IMainMenuView
|
||||
{
|
||||
[SerializeField] private GameObject root;
|
||||
[SerializeField] private Button startButton;
|
||||
|
||||
public event Action StartClicked;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
root = gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (startButton != null)
|
||||
{
|
||||
startButton.onClick.AddListener(OnStartClicked);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (startButton != null)
|
||||
{
|
||||
startButton.onClick.RemoveListener(OnStartClicked);
|
||||
}
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
root.SetActive(true);
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
root.SetActive(false);
|
||||
}
|
||||
|
||||
private void OnStartClicked()
|
||||
{
|
||||
StartClicked?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb899c7e47cd4e341b0258dac3f7a238
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Minesweeper.Core;
|
||||
|
||||
namespace Minesweeper.Presentation.Views
|
||||
{
|
||||
@@ -15,5 +17,65 @@ namespace Minesweeper.Presentation.Views
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event Action PauseRequested
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event Action ResumeRequested
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event Action<int, int> CellOpenRequested
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event Action<int, int> CellFlagRequested
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public void ShowGame()
|
||||
{
|
||||
}
|
||||
|
||||
public void HideGame()
|
||||
{
|
||||
}
|
||||
|
||||
public void ShowPause()
|
||||
{
|
||||
}
|
||||
|
||||
public void HidePause()
|
||||
{
|
||||
}
|
||||
|
||||
public void SetMineCount(int minesCount)
|
||||
{
|
||||
}
|
||||
|
||||
public void SetTimer(float seconds)
|
||||
{
|
||||
}
|
||||
|
||||
public void RebuildBoard(IReadOnlyList<BoardCellData> cells, int width, int height)
|
||||
{
|
||||
}
|
||||
|
||||
public void RefreshBoard(IReadOnlyList<BoardCellData> cells)
|
||||
{
|
||||
}
|
||||
|
||||
public void SetBoardInputEnabled(bool enabled)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,13 @@ namespace Minesweeper.Presentation.Views
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user