- L and T shapes are no longer considered a match - Bomb explodes WITH gems - Bomb will have a small sprite of the creating gem
135 lines
4.5 KiB
C#
135 lines
4.5 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>();
|
|
|
|
|
|
return matchPositions.Distinct().ToList();
|
|
}
|
|
|
|
public async UniTask DetonateChainAsync(
|
|
IReadOnlyList<Vector2Int> initialBombs,
|
|
Func<Vector2Int, bool> inBounds,
|
|
Func<Vector2Int, Gem> getGemAt,
|
|
Func<Vector2Int, UniTask> destroyAtAsync,
|
|
int radius,
|
|
float bombDelaySeconds)
|
|
{
|
|
if (initialBombs == null || initialBombs.Count == 0)
|
|
return;
|
|
|
|
int waveDelayMs = Mathf.Max(0, Mathf.RoundToInt(bombDelaySeconds * 1000f));
|
|
|
|
HashSet<Vector2Int> processedBombs = new HashSet<Vector2Int>();
|
|
|
|
Queue<Vector2Int> waveQueue = new Queue<Vector2Int>(
|
|
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<Vector2Int> waveBombs = new List<Vector2Int>();
|
|
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<Vector2Int> nextWaveBombs = new HashSet<Vector2Int>();
|
|
HashSet<Vector2Int> toDestroyNow = new HashSet<Vector2Int>();
|
|
|
|
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<Vector2Int> 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |