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

@@ -22,10 +22,6 @@ namespace Services
private BombSpawnRequest? pendingBombSpawn; private BombSpawnRequest? pendingBombSpawn;
public BombSpawnRequest? PendingBombSpawn => this.pendingBombSpawn; public BombSpawnRequest? PendingBombSpawn => this.pendingBombSpawn;
public void ClearPendingBombs() {
this.pendingBombSpawn = null;
}
public BombService(GameVariables gameVariables, IGameBoard gameBoard) { public BombService(GameVariables gameVariables, IGameBoard gameBoard) {
this.gameVariables = gameVariables; this.gameVariables = gameVariables;
this.gameBoard = gameBoard; this.gameBoard = gameBoard;
@@ -34,6 +30,38 @@ namespace Services
public void SetLastSwap(Vector2Int from, Vector2Int to) { public void SetLastSwap(Vector2Int from, Vector2Int to) {
this.lastSwapFrom = from; this.lastSwapFrom = from;
this.lastSwapTo = to; 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) { public void DetectBombSpawnFromLastSwap(List<Gem> currentMatches) {
@@ -201,5 +229,9 @@ namespace Services
return count; 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 //Uses the ObjectPool to spawn a gem at the given position
private void SpawnGem(Vector2Int position, GemType gemType, bool isBomb = false) { private void SpawnGem(Vector2Int position, GemType gemType, bool isBomb = false) {
if (isBomb) { if (isBomb) {
DestroyMatchedGems(position); ReleaseMatchedGems(position);
} }
GemView gemView = this.objectPool.Get(isBomb ? GemType.Bomb : gemType, position, isBomb ? 0 : this.gameVariables.dropHeight); GemView gemView = this.objectPool.Get(isBomb ? GemType.Bomb : gemType, position, isBomb ? 0 : this.gameVariables.dropHeight);
@@ -122,7 +122,7 @@ namespace Services {
if (!GemUtils.IsInBounds(from, this.gameBoard) || !GemUtils.IsInBounds(to, this.gameBoard)) if (!GemUtils.IsInBounds(from, this.gameBoard) || !GemUtils.IsInBounds(to, this.gameBoard))
return false; return false;
if (!AreAdjacentCardinal(from, to)) if (!from.IsAdjacent(to))
return false; return false;
this.currentState = GameState.Wait; this.currentState = GameState.Wait;
@@ -131,7 +131,6 @@ namespace Services {
await UniTask.Delay(600); await UniTask.Delay(600);
this.bombService.SetLastSwap(from, to); this.bombService.SetLastSwap(from, to);
this.bombService.ClearPendingBombs();
this.matchService.FindAllMatches(); this.matchService.FindAllMatches();
this.bombService.DetectBombSpawnFromLastSwap(this.matchService.CurrentMatches); this.bombService.DetectBombSpawnFromLastSwap(this.matchService.CurrentMatches);
@@ -142,7 +141,7 @@ namespace Services {
return false; return false;
} }
List<Vector2Int> protectedPositions = ApplyPendingBombSpawns(); List<Vector2Int> protectedPositions = this.bombService.ApplyPendingBombSpawns(SpawnGem);
await DestroyMatchesAsync(protectedPositions); await DestroyMatchesAsync(protectedPositions);
this.currentState = GameState.Move; this.currentState = GameState.Move;
return true; return true;
@@ -160,47 +159,9 @@ namespace Services {
this.gameBoard.SetGemAt(to, fromGem); 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) { private async UniTask DestroyMatchesAsync(List<Vector2Int> protectedPositions) {
List<Vector2Int> matchPositions = new List<Vector2Int>(this.matchService.CurrentMatches.Count); List<Vector2Int> matchPositions = await this.matchService.GetMatchPositionsAsync(protectedPositions);
for (int i = 0; i < this.matchService.CurrentMatches.Count; i++) { List<Vector2Int> initialBombs = await this.bombService.GetInitialBombs(protectedPositions, matchPositions.Distinct().ToList());
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();
// If a bomb is part of the match, do NOT destroy matching pieces immediately. // 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. // Let the bomb's manhattan-distance explosion destroy them in sequence.
@@ -210,29 +171,27 @@ namespace Services {
DestroyAtAsync, DestroyAtAsync,
this.gameBoard); this.gameBoard);
await UniTask.Delay(600);
await MoveGemsDown(); await MoveGemsDown();
return; return;
} }
// For audio SFX
bool willBreakAnyNonBombGem = matchPositions.Select(pos => this.gameBoard.GetGemAt(pos)).Where(gem => gem != null).Any(gem => gem.Type != GemType.Bomb); bool willBreakAnyNonBombGem = matchPositions.Select(pos => this.gameBoard.GetGemAt(pos)).Where(gem => gem != null).Any(gem => gem.Type != GemType.Bomb);
if (willBreakAnyNonBombGem) if (willBreakAnyNonBombGem)
this.audioPresenter.OnMatch(this.gameVariables.matchSfx); this.audioPresenter.OnMatch(this.gameVariables.matchSfx);
// For score counting
foreach (Vector2Int pos in matchPositions.Distinct().ToList()) { foreach (Vector2Int pos in matchPositions.Distinct().ToList()) {
Gem gem = this.gameBoard.GetGemAt(pos); Gem gem = this.gameBoard.GetGemAt(pos);
if (gem == null) continue; if (gem == null) continue;
if (gem.Type == GemType.Bomb) continue; if (gem.Type == GemType.Bomb) continue;
this.scoreService.ScoreCheck(gem.ScoreValue); this.scoreService.ScoreCheck(gem.ScoreValue);
DestroyMatchedGems(pos); ReleaseMatchedGems(pos);
} }
await this.bombService.DetonateChainAsync(
initialBombs,
DestroyAtAsync,
this.gameBoard);
await MoveGemsDown(); await MoveGemsDown();
} }
@@ -245,10 +204,26 @@ namespace Services {
this.audioPresenter.OnBombExplosion(this.gameVariables.bombExplodeSfx); this.audioPresenter.OnBombExplosion(this.gameVariables.bombExplodeSfx);
this.scoreService.ScoreCheck(gem.ScoreValue); this.scoreService.ScoreCheck(gem.ScoreValue);
DestroyMatchedGems(pos); ReleaseMatchedGems(pos);
return UniTask.CompletedTask; 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() { private async UniTask MoveGemsDown() {
await UniTask.Delay(50); await UniTask.Delay(50);
@@ -314,22 +289,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 #region Utils
private void RemovePresenterFor(GemView gemView) { private void RemovePresenterFor(GemView gemView) {
if (gemView is null) { if (gemView is null) {
@@ -339,11 +298,6 @@ namespace Services {
GemPresenter presenter = this.gemPresenters.FirstOrDefault(p => p.GemView == gemView); GemPresenter presenter = this.gemPresenters.FirstOrDefault(p => p.GemView == gemView);
this.gemPresenters.Remove(presenter); 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 #endregion
public void Dispose() { public void Dispose() {

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Cysharp.Threading.Tasks;
using Enums; using Enums;
using Models.Interfaces; using Models.Interfaces;
using Services.Interfaces; using Services.Interfaces;
@@ -50,6 +51,22 @@ namespace Services {
return false; 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() { public void FindAllMatches() {
this.currentMatches.Clear(); this.currentMatches.Clear();

View File

@@ -14,5 +14,10 @@ namespace Utils {
public static Vector2Int ToVector2Int(this Vector2 v) { public static Vector2Int ToVector2Int(this Vector2 v) {
return new Vector2Int((int)v.x, (int)v.y); 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);
}
} }
} }