117 lines
4.1 KiB
C#
117 lines
4.1 KiB
C#
// 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<Vector2Int> CollectTriggeredBombs(IReadOnlyList<Vector2Int> matchPositions)
|
||
{
|
||
if (matchPositions == null || matchPositions.Count == 0)
|
||
return Array.Empty<Vector2Int>();
|
||
|
||
// 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<Vector2Int> candidates = new HashSet<Vector2Int>(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<Vector2Int> initialBombs,
|
||
Func<Vector2Int, bool> inBounds,
|
||
Func<Vector2Int, Gem> getGemAt,
|
||
Func<Vector2Int, UniTask> destroyAtAsync,
|
||
int radius,
|
||
float bombDelaySeconds,
|
||
float bombSelfDelaySeconds)
|
||
{
|
||
if (initialBombs == null || initialBombs.Count == 0)
|
||
return;
|
||
|
||
Queue<Vector2Int> queue = new Queue<Vector2Int>(initialBombs);
|
||
HashSet<Vector2Int> processed = new HashSet<Vector2Int>();
|
||
|
||
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<Vector2Int> 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;
|
||
}
|
||
}
|
||
}
|
||
} |