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 cellsByCoordinate = new Dictionary(); 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 CellOpenRequested; public event Action 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 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 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(); label.alignment = TextAlignmentOptions.Center; label.enableAutoSizing = true; label.fontSizeMin = 6f; label.fontSizeMax = 32f; label.color = Color.black; var view = go.AddComponent(); view.Bind(go.GetComponent