using System.Collections.Generic; using System.Linq; using Enums; using Models.Interfaces; using Services.Interfaces; using Structs; using UnityEngine; namespace Services { public class MatchService : IMatchService { private List currentMatches = new List(); public List CurrentMatches => this.currentMatches; public List pendingBombSpawns = new List(); public IReadOnlyList PendingBombSpawns => this.pendingBombSpawns; private Vector2Int lastSwapFrom; private Vector2Int lastSwapTo; private IGameBoard gameBoard; public MatchService(IGameBoard gameBoard) { this.gameBoard = gameBoard; } public void SetLastSwap(Vector2Int from, Vector2Int to) { this.lastSwapFrom = from; this.lastSwapTo = to; } public void ClearPendingBombs() { this.pendingBombSpawns.Clear(); } public bool MatchesAt(Vector2Int positionToCheck, GemType gemTypeToCheck) { Gem[,] gems = this.gameBoard.GemsGrid; // We don't prevent spawning bombs via this rule (and bombs shouldn't be treated as a normal color here). if (gemTypeToCheck == GemType.Bomb) return false; // Check horizontal: would placing gemTypeToCheck at positionToCheck create XXX with 2-left? if (positionToCheck.x > 1) { Gem left1 = gems[positionToCheck.x - 1, positionToCheck.y]; Gem left2 = gems[positionToCheck.x - 2, positionToCheck.y]; if (left1 != null && left2 != null && left1.MatchColor == gemTypeToCheck && left2.MatchColor == gemTypeToCheck) return true; } // Check vertical: would placing gemTypeToCheck at positionToCheck create XXX with 2-down? if (positionToCheck.y > 1) { Gem down1 = gems[positionToCheck.x, positionToCheck.y - 1]; Gem down2 = gems[positionToCheck.x, positionToCheck.y - 2]; if (down1 != null && down2 != null && down1.MatchColor == gemTypeToCheck && down2.MatchColor == gemTypeToCheck) return true; } return false; } public void FindAllMatches() { this.currentMatches.Clear(); this.pendingBombSpawns.Clear(); for (int x = 0; x < this.gameBoard.Width; x++) for (int y = 0; y < this.gameBoard.Height; y++) { Gem currentGem = this.gameBoard.GemsGrid[x, y]; if (currentGem == null) continue; if (x > 0 && x < this.gameBoard.Width - 1) { Gem leftGem = this.gameBoard.GemsGrid[x - 1, y]; Gem rightGem = this.gameBoard.GemsGrid[x + 1, y]; if (leftGem != null && rightGem != null) { if (leftGem.MatchColor == currentGem.MatchColor && rightGem.MatchColor == currentGem.MatchColor) { this.currentMatches.Add(currentGem); this.currentMatches.Add(leftGem); this.currentMatches.Add(rightGem); } } } if (y > 0 && y < this.gameBoard.Height - 1) { Gem aboveGem = this.gameBoard.GemsGrid[x, y - 1]; Gem bellowGem = this.gameBoard.GemsGrid[x, y + 1]; if (aboveGem != null && bellowGem != null) { if (aboveGem.MatchColor == currentGem.MatchColor && bellowGem.MatchColor == currentGem.MatchColor) { this.currentMatches.Add(currentGem); this.currentMatches.Add(aboveGem); this.currentMatches.Add(bellowGem); } } } } if (this.currentMatches.Count > 0) this.currentMatches = this.currentMatches.Distinct().ToList(); DetectBombSpawnFromLastSwap(); } private void DetectBombSpawnFromLastSwap() { Vector2Int from = this.lastSwapFrom; Vector2Int to = this.lastSwapTo; TryCreateBombSpawnAt(from); TryCreateBombSpawnAt(to); } private void TryCreateBombSpawnAt(Vector2Int pivot) { Gem pivotGem = this.gameBoard.GetGemAt(pivot); if (pivotGem == null) return; // If it's already a bomb, don't create another. if (pivotGem.Type == GemType.Bomb) return; if (this.currentMatches.All(g => g.Position != pivot)) return; // If the matched group that includes this pivot has 4+ connected gems, spawn a bomb. int groupSize = GetMatchedGroupSize(pivot); if (groupSize < 4) return; // Prevent duplicates for the same cell. if (this.pendingBombSpawns.Any(b => b.Position == pivot)) return; this.pendingBombSpawns.Add(new BombSpawnRequest(pivot, pivotGem.MatchColor)); } private int GetMatchedGroupSize(Vector2Int pivot) { Gem pivotGem = this.gameBoard.GetGemAt(pivot); if (pivotGem == null) return 0; GemType color = pivotGem.MatchColor; HashSet matchedPositions = new HashSet( this.currentMatches .Where(g => g != null && g.MatchColor == color) .Select(g => g.Position) ); if (!matchedPositions.Contains(pivot)) return 0; Queue queue = new Queue(); HashSet visited = new HashSet(); queue.Enqueue(pivot); visited.Add(pivot); Vector2Int[] directions = { Vector2Int.left, Vector2Int.right, Vector2Int.up, Vector2Int.down }; while (queue.Count > 0) { Vector2Int currentPivot = queue.Dequeue(); for (int i = 0; i < directions.Length; i++) { Vector2Int n = currentPivot + directions[i]; if (visited.Contains(n)) continue; if (!matchedPositions.Contains(n)) continue; visited.Add(n); queue.Enqueue(n); } } return visited.Count; } } }