// Assets/Scripts/Services/BombService.cs using System; using System.Collections.Generic; using System.Linq; using Cysharp.Threading.Tasks; using Enums; using Models; using Services.Interfaces; using UnityEngine; namespace Services { public class BombService : IBombService { public IReadOnlyList CollectTriggeredBombs(IReadOnlyList matchPositions) { if (matchPositions == null || matchPositions.Count == 0) return Array.Empty(); return matchPositions.Distinct().ToList(); } // This is the old implementation wherein any adjacent matches will detonate the bomb private List DetonateThroughAdjacentMatches(IReadOnlyList matchPositions) { HashSet candidates = new HashSet(); foreach (Vector2Int p in matchPositions) { candidates.Add(p + Vector2Int.left); candidates.Add(p + Vector2Int.right); candidates.Add(p + Vector2Int.up); candidates.Add(p + Vector2Int.down); } return candidates.ToList(); } public async UniTask DetonateChainAsync( IReadOnlyList initialBombs, Func inBounds, Func getGemAt, Func destroyAtAsync, int radius, float bombDelaySeconds) { if (initialBombs == null || initialBombs.Count == 0) return; int waveDelayMs = Mathf.Max(0, Mathf.RoundToInt(bombDelaySeconds * 1000f)); HashSet processedBombs = new HashSet(); Queue waveQueue = new Queue( initialBombs.Where(p => { if (!inBounds(p)) return false; Gem g = getGemAt(p); return g is { Type: GemType.Bomb }; }) ); while (waveQueue.Count > 0) { // current wave (per bomb) List waveBombs = new List(); while (waveQueue.Count > 0) { Vector2Int b = waveQueue.Dequeue(); if (processedBombs.Contains(b)) continue; if (!inBounds(b)) continue; Gem g = getGemAt(b); if (g is not { Type: GemType.Bomb }) continue; processedBombs.Add(b); waveBombs.Add(b); } if (waveBombs.Count == 0) continue; // delay once per wave if (waveDelayMs > 0) await UniTask.Delay(waveDelayMs); HashSet nextWaveBombs = new HashSet(); HashSet toDestroyNow = new HashSet(); for (int i = 0; i < waveBombs.Count; i++) { Vector2Int bombPos = waveBombs[i]; // destroy self when it detonates toDestroyNow.Add(bombPos); foreach (Vector2Int p in DiamondAreaInclusive(bombPos, radius)) { if (!inBounds(p)) continue; if (p == bombPos) continue; Gem cellGem = getGemAt(p); if (cellGem == null) continue; if (cellGem.Type == GemType.Bomb) { // bombs in range are NOT destroyed now. triggered to explode in a later wave. if (!processedBombs.Contains(p)) nextWaveBombs.Add(p); continue; } // Non-bomb gem gets destroyed by this bomb toDestroyNow.Add(p); } } // Destroy everything for this wave (non-bombs in range + the detonating bombs themselves) foreach (Vector2Int p in toDestroyNow) await destroyAtAsync(p); // Schedule the next wave (triggered bombs) foreach (Vector2Int b in nextWaveBombs) waveQueue.Enqueue(b); } } private static IEnumerable DiamondRing(Vector2Int center, int distance) { for (int distanceX = -distance; distanceX <= distance; distanceX++) { int distanceY = distance - Mathf.Abs(distanceX); if (distanceY == 0) { yield return new Vector2Int(center.x + distanceX, center.y); } else { yield return new Vector2Int(center.x + distanceX, center.y + distanceY); yield return new Vector2Int(center.x + distanceX, center.y - distanceY); } } } private static IEnumerable DiamondAreaInclusive(Vector2Int center, int radius) { yield return center; for (int dist = 1; dist <= radius; dist++) { foreach (Vector2Int pivot in DiamondRing(center, dist)) yield return pivot; } } } }