// 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(); } 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 DiamondAreaInclusive(Vector2Int center, int radius) { // Manhattan-distance filled diamond: for (int distanceX = -radius; distanceX <= radius; distanceX++) { int remaining = radius - Mathf.Abs(distanceX); for (int distanceY = -remaining; distanceY <= remaining; distanceY++) { yield return new Vector2Int(center.x + distanceX, center.y + distanceY); } } } } }