Separate bomb logic

This commit is contained in:
2025-12-15 04:45:17 +08:00
parent 5236df963e
commit f4a2cac16d
6 changed files with 191 additions and 89 deletions

View File

@@ -0,0 +1,117 @@
// 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;
}
}
}
}