Files
match3-unity/Assets/Scripts/Services/BombService.cs
2025-12-15 04:45:17 +08:00

117 lines
4.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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, well 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;
}
}
}
}