// 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(); // Activation: any match cell that is a bomb OR cardinal-adjacent to a bomb. // NOTE: The actual "is bomb?" check depends on the board, so we only return // the positions to be checked/queued by caller if desired. // To keep BombService isolated, we’ll let DetonateChainAsync validate bombs via getGemAt. // Here we return: all matched positions + their cardinal neighbors. HashSet candidates = new HashSet(matchPositions); 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, float bombSelfDelaySeconds) { if (initialBombs == null || initialBombs.Count == 0) return; Queue queue = new Queue(initialBombs); HashSet processed = new HashSet(); while (queue.Count > 0) { Vector2Int bombPos = queue.Dequeue(); if (processed.Contains(bombPos)) continue; if (!inBounds(bombPos)) continue; Gem bomb = getGemAt(bombPos); if (bomb is not { Type: GemType.Bomb }) continue; processed.Add(bombPos); // Delay before neighbor blast int neighborDelayMs = Mathf.Max(0, Mathf.RoundToInt(bombDelaySeconds * 1000f)); if (neighborDelayMs > 0) await UniTask.Delay(neighborDelayMs); // Blast neighbors first (cross) foreach (Vector2Int n in CrossNeighbors(bombPos, radius)) { if (!inBounds(n)) continue; Gem g = getGemAt(n); if (g == null) continue; // Chain: if another bomb is in blast area, queue it if (g.Type == GemType.Bomb) { if (!processed.Contains(n)) queue.Enqueue(n); continue; } await destroyAtAsync(n); } // Delay before destroying the bomb itself int selfDelayMs = Mathf.Max(0, Mathf.RoundToInt(bombSelfDelaySeconds * 1000f)); if (selfDelayMs > 0) await UniTask.Delay(selfDelayMs); // Destroy bomb last Gem stillBomb = getGemAt(bombPos); if (stillBomb is { Type: GemType.Bomb }) await destroyAtAsync(bombPos); } } private static IEnumerable CrossNeighbors(Vector2Int center, int radius) { for (int i = 1; i <= radius; i++) { yield return center + Vector2Int.left * i; yield return center + Vector2Int.right * i; yield return center + Vector2Int.up * i; yield return center + Vector2Int.down * i; } } } }