Cleanup GameBOardService

This commit is contained in:
2025-12-17 06:34:57 +08:00
parent b3dc2cb4bd
commit 2de9359dda
6 changed files with 91 additions and 79 deletions

View File

@@ -21,10 +21,6 @@ namespace Services
private BombSpawnRequest? pendingBombSpawn;
public BombSpawnRequest? PendingBombSpawn => this.pendingBombSpawn;
public void ClearPendingBombs() {
this.pendingBombSpawn = null;
}
public BombService(GameVariables gameVariables, IGameBoard gameBoard) {
this.gameVariables = gameVariables;
@@ -34,6 +30,38 @@ namespace Services
public void SetLastSwap(Vector2Int from, Vector2Int to) {
this.lastSwapFrom = from;
this.lastSwapTo = to;
ClearPendingBombs();
}
public UniTask<List<Vector2Int>> GetInitialBombs(List<Vector2Int> protectedPositions, List<Vector2Int> bombCandidates) {
List<Vector2Int> initialBombs = new List<Vector2Int>();
foreach (Vector2Int p in bombCandidates) {
if (!GemUtils.IsInBounds(p, this.gameBoard)) continue;
if (protectedPositions != null && protectedPositions.Contains(p))
continue;
Gem gem = this.gameBoard.GetGemAt(p);
if (gem is { Type: GemType.Bomb })
initialBombs.Add(p);
}
return UniTask.FromResult(initialBombs.Distinct().ToList());
}
public List<Vector2Int> ApplyPendingBombSpawns(Action<Vector2Int, GemType, bool> spawnGem) {
List<Vector2Int> positions = new List<Vector2Int>();
BombSpawnRequest? bombSpawnRequest = PendingBombSpawn;
if (bombSpawnRequest != null) {
BombSpawnRequest bombRequest = PendingBombSpawn.GetValueOrDefault();
positions.Add(bombRequest.Position);
spawnGem(bombRequest.Position, bombRequest.Color, true);
}
ClearPendingBombs();
return positions;
}
public void DetectBombSpawnFromLastSwap(List<Gem> currentMatches) {
@@ -201,5 +229,9 @@ namespace Services
return count;
}
public void ClearPendingBombs() {
this.pendingBombSpawn = null;
}
}
}

View File

@@ -100,7 +100,7 @@ namespace Services {
//Uses the ObjectPool to spawn a gem at the given position
private void SpawnGem(Vector2Int position, GemType gemType, bool isBomb = false) {
if (isBomb) {
DestroyMatchedGems(position);
ReleaseMatchedGems(position);
}
GemView gemView = this.objectPool.Get(isBomb ? GemType.Bomb : gemType, position, isBomb ? 0 : this.gameVariables.dropHeight);
@@ -121,8 +121,8 @@ namespace Services {
if (!GemUtils.IsInBounds(from, this.gameBoard) || !GemUtils.IsInBounds(to, this.gameBoard))
return false;
if (!AreAdjacentCardinal(from, to))
if (!from.IsAdjacent(to))
return false;
this.currentState = GameState.Wait;
@@ -131,7 +131,6 @@ namespace Services {
await UniTask.Delay(600);
this.bombService.SetLastSwap(from, to);
this.bombService.ClearPendingBombs();
this.matchService.FindAllMatches();
this.bombService.DetectBombSpawnFromLastSwap(this.matchService.CurrentMatches);
@@ -142,7 +141,7 @@ namespace Services {
return false;
}
List<Vector2Int> protectedPositions = ApplyPendingBombSpawns();
List<Vector2Int> protectedPositions = this.bombService.ApplyPendingBombSpawns(SpawnGem);
await DestroyMatchesAsync(protectedPositions);
this.currentState = GameState.Move;
return true;
@@ -160,47 +159,9 @@ namespace Services {
this.gameBoard.SetGemAt(to, fromGem);
}
private List<Vector2Int> ApplyPendingBombSpawns() {
List<Vector2Int> positions = new List<Vector2Int>();
BombSpawnRequest? bombSpawnRequest = this.bombService.PendingBombSpawn;
if (bombSpawnRequest != null) {
BombSpawnRequest bombRequest = this.bombService.PendingBombSpawn.GetValueOrDefault();
positions.Add(bombRequest.Position);
SpawnGem(bombRequest.Position, bombRequest.Color, isBomb: true);
}
this.bombService.ClearPendingBombs();
return positions;
}
private async UniTask DestroyMatchesAsync(List<Vector2Int> protectedPositions) {
List<Vector2Int> matchPositions = new List<Vector2Int>(this.matchService.CurrentMatches.Count);
for (int i = 0; i < this.matchService.CurrentMatches.Count; i++) {
Gem match = this.matchService.CurrentMatches[i];
if (match == null) continue;
Vector2Int pos = match.Position;
if (protectedPositions != null && protectedPositions.Contains(pos))
continue;
matchPositions.Add(pos);
}
IReadOnlyList<Vector2Int> bombCandidates = matchPositions.Distinct().ToList();
List<Vector2Int> initialBombs = new List<Vector2Int>();
foreach (Vector2Int p in bombCandidates) {
if (!GemUtils.IsInBounds(p, this.gameBoard)) continue;
if (protectedPositions != null && protectedPositions.Contains(p))
continue;
Gem gem = this.gameBoard.GetGemAt(p);
if (gem is { Type: GemType.Bomb })
initialBombs.Add(p);
}
initialBombs = initialBombs.Distinct().ToList();
List<Vector2Int> matchPositions = await this.matchService.GetMatchPositionsAsync(protectedPositions);
List<Vector2Int> initialBombs = await this.bombService.GetInitialBombs(protectedPositions, matchPositions.Distinct().ToList());
// If a bomb is part of the match, do NOT destroy matching pieces immediately.
// Let the bomb's manhattan-distance explosion destroy them in sequence.
@@ -210,29 +171,27 @@ namespace Services {
DestroyAtAsync,
this.gameBoard);
await UniTask.Delay(600);
await MoveGemsDown();
return;
}
// For audio SFX
bool willBreakAnyNonBombGem = matchPositions.Select(pos => this.gameBoard.GetGemAt(pos)).Where(gem => gem != null).Any(gem => gem.Type != GemType.Bomb);
if (willBreakAnyNonBombGem)
this.audioPresenter.OnMatch(this.gameVariables.matchSfx);
// For score counting
foreach (Vector2Int pos in matchPositions.Distinct().ToList()) {
Gem gem = this.gameBoard.GetGemAt(pos);
if (gem == null) continue;
if (gem.Type == GemType.Bomb) continue;
this.scoreService.ScoreCheck(gem.ScoreValue);
DestroyMatchedGems(pos);
ReleaseMatchedGems(pos);
}
await this.bombService.DetonateChainAsync(
initialBombs,
DestroyAtAsync,
this.gameBoard);
await MoveGemsDown();
}
@@ -245,9 +204,25 @@ namespace Services {
this.audioPresenter.OnBombExplosion(this.gameVariables.bombExplodeSfx);
this.scoreService.ScoreCheck(gem.ScoreValue);
DestroyMatchedGems(pos);
ReleaseMatchedGems(pos);
return UniTask.CompletedTask;
}
private void ReleaseMatchedGems(Vector2Int position) {
List<GemView> gemsViews = this.gemsHolder.GetComponentsInChildren<GemView>().ToList();
Gem currentGem = this.gameBoard.GetGemAt(position);
if (currentGem != null)
{
GemView gemView = gemsViews.FirstOrDefault(gv => gv.Gem == currentGem);
if (gemView is null) {
return;
}
this.objectPool.Release(gemView);
RemovePresenterFor(gemView);
this.gameBoard.SetGemAt(position, null);
}
}
private async UniTask MoveGemsDown() {
await UniTask.Delay(50);
@@ -313,22 +288,6 @@ namespace Services {
}
}
}
private void DestroyMatchedGems(Vector2Int position) {
List<GemView> gemsViews = this.gemsHolder.GetComponentsInChildren<GemView>().ToList();
Gem currentGem = this.gameBoard.GetGemAt(position);
if (currentGem != null)
{
GemView gemView = gemsViews.FirstOrDefault(gv => gv.Gem == currentGem);
if (gemView is null) {
return;
}
this.objectPool.Release(gemView);
RemovePresenterFor(gemView);
this.gameBoard.SetGemAt(position, null);
}
}
#region Utils
private void RemovePresenterFor(GemView gemView) {
@@ -339,11 +298,6 @@ namespace Services {
GemPresenter presenter = this.gemPresenters.FirstOrDefault(p => p.GemView == gemView);
this.gemPresenters.Remove(presenter);
}
private static bool AreAdjacentCardinal(Vector2Int a, Vector2Int b) {
Vector2Int d = b - a;
return (Mathf.Abs(d.x) == 1 && d.y == 0) || (Mathf.Abs(d.y) == 1 && d.x == 0);
}
#endregion
public void Dispose() {

View File

@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using Enums;
using Models.Interfaces;
using Structs;
using UnityEngine;
@@ -12,9 +13,10 @@ namespace Services.Interfaces
public BombSpawnRequest? PendingBombSpawn { get; }
void SetLastSwap(Vector2Int from, Vector2Int to);
void ClearPendingBombs();
void DetectBombSpawnFromLastSwap(List<Gem> currentMatches);
List<Vector2Int> ApplyPendingBombSpawns(Action<Vector2Int, GemType, bool> spawnGem);
UniTask<List<Vector2Int>> GetInitialBombs(List<Vector2Int> protectedPositions, List<Vector2Int> bombCandidates);
UniTask DetonateChainAsync(
IReadOnlyList<Vector2Int> initialBombs,

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
using Enums;
using Structs;
@@ -6,6 +7,7 @@ using Structs;
namespace Services.Interfaces {
public interface IMatchService {
List<Gem> CurrentMatches { get; }
UniTask<List<Vector2Int>> GetMatchPositionsAsync(List<Vector2Int> protectedPositions);
bool MatchesAt(Vector2Int positionToCheck, GemType gemTypeToCheck);
void FindAllMatches();
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
using Enums;
using Models.Interfaces;
using Services.Interfaces;
@@ -50,6 +51,22 @@ namespace Services {
return false;
}
public UniTask<List<Vector2Int>> GetMatchPositionsAsync(List<Vector2Int> protectedPositions) {
List<Vector2Int> matchPositions = new List<Vector2Int>(CurrentMatches.Count);
for (int i = 0; i < CurrentMatches.Count; i++) {
Gem match = CurrentMatches[i];
if (match == null) continue;
Vector2Int pos = match.Position;
if (protectedPositions != null && protectedPositions.Contains(pos))
continue;
matchPositions.Add(pos);
}
return UniTask.FromResult(matchPositions);
}
public void FindAllMatches() {
this.currentMatches.Clear();

View File

@@ -14,5 +14,10 @@ namespace Utils {
public static Vector2Int ToVector2Int(this Vector2 v) {
return new Vector2Int((int)v.x, (int)v.y);
}
public static bool IsAdjacent(this Vector2Int a, Vector2Int b) {
Vector2Int d = b - a;
return (Mathf.Abs(d.x) == 1 && d.y == 0) || (Mathf.Abs(d.y) == 1 && d.x == 0);
}
}
}