Implement Bombs

This commit is contained in:
2025-12-15 02:34:59 +08:00
parent 95b43ed772
commit abff76e4ab
36 changed files with 512 additions and 753 deletions

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
@@ -14,31 +15,38 @@ using Object = UnityEngine.Object;
using Random = UnityEngine.Random;
namespace Services {
public class GameBoardService : IGameBoardService, ITickable {
public class GameBoardService : IGameBoardService, ITickable, IDisposable {
#region Inject
private readonly IGameBoard gameBoard;
private readonly GameVariables gameVariables;
private readonly IMatchService matchService;
private readonly IScoreService scoreService;
private readonly IObjectPool<GemView> objectPool;
private readonly Transform gemsHolder;
#endregion
#region Variables
private readonly List<GemPresenter> gemPresenters = new List<GemPresenter>();
private readonly ScorePresenter scorePresenter;
private GameState currentState = GameState.Move;
#endregion
public GameBoardService(IGameBoard gameBoard, GameVariables gameVariables, IMatchService matchService, IScoreService scoreSerivce, IObjectPool<GemView> objectPool, Transform gemsHolder) {
public GameBoardService(IGameBoard gameBoard, GameVariables gameVariables, IMatchService matchService, IScoreService scoreSerivce, IObjectPool<GemView> objectPool, Transform gemsHolder, ScorePresenter scorePresenter) {
this.gameBoard = gameBoard;
this.gameVariables = gameVariables;
this.matchService = matchService;
this.scoreService = scoreSerivce;
this.objectPool = objectPool;
this.gemsHolder = gemsHolder;
this.scorePresenter = scorePresenter;
}
public void Tick() {
int i = 0;
foreach (GemPresenter gemPresenter in gemPresenters) {
gemPresenter.Tick();
i++;
}
this.scorePresenter.Tick();
}
//Instantiates background tiles and calls SpawnGems
@@ -61,18 +69,20 @@ namespace Services {
iterations++;
}
SpawnGem(new Vector2Int(x, y), this.gameVariables.gemsPrefabs[gemToUse], (GemType)gemToUse);
SpawnGem(new Vector2Int(x, y), (GemType)gemToUse);
}
this.currentState = GameState.Move;
}
//Uses the ObjectPool to spawn a gem at the given position
public void SpawnGem(Vector2Int position, GemView gemPrefab, GemType gemType) {
private void SpawnGem(Vector2Int position, GemType gemType) {
if (Random.Range(0, 100f) < this.gameVariables.bombChance)
gemPrefab = this.gameVariables.bombPrefab;
gemType = GemType.Bomb;
GemView gemView = this.objectPool.Get(gemType, position, this.gameVariables.dropHeight);
gemView.name = "Gem - " + position.x + ", " + position.y;
Gem gem = new Gem(gemType, position);
gemView.name = "Gem - " + position.x + ", " + position.y + ' ' + gemType;
Gem gem = new Gem(gemType, position, 50);
gemView.Bind(gem);
this.gemPresenters.Add(new GemPresenter(gem, gemView));
@@ -80,17 +90,65 @@ namespace Services {
}
//Sets the gem on the GameBoard
public void SetGem(Vector2Int position, Gem gem) {
private void SetGem(Vector2Int position, Gem gem) {
this.gameBoard.SetGemAt(new Vector2Int(position.x, position.y), gem);
}
//Gets the gem from the GameBoard
public Gem GetGem(Vector2Int position) {
private Gem GetGem(Vector2Int position) {
return this.gameBoard.GetGemAt(position);
}
//Listens to InputService OnSwapRequest
public async UniTask<bool> TrySwap(Vector2Int from, Vector2Int to) {
if (this.currentState != GameState.Move)
return false;
if (!InBounds(from) || !InBounds(to))
return false;
if (!AreAdjacentCardinal(from, to))
return false;
Gem fromGem = GetGem(from);
Gem toGem = GetGem(to);
if(fromGem == null || toGem == null)
return false;
this.currentState = GameState.Wait;
ApplySwap(from, to, fromGem, toGem);
await UniTask.Delay(600);
this.matchService.FindAllMatches();
bool hasMatch = this.matchService.CurrentMatches.Count > 0;
if (!hasMatch) {
ApplySwap(to, from, fromGem, toGem);
await UniTask.Delay(600);
this.currentState = GameState.Move;
return false;
}
DestroyMatches();
this.currentState = GameState.Move;
return true;
}
private void ApplySwap(Vector2Int posA, Vector2Int posB, Gem gemA, Gem gemB) {
// swap their stored positions
gemA.SetPosition(posB);
gemB.SetPosition(posA);
// update grid
SetGem(posA, gemB);
SetGem(posB, gemA);
}
//If there are matches, destroys them and moves the gems down
public void DestroyMatches() {
private void DestroyMatches() {
for (int i = 0; i < this.matchService.CurrentMatches.Count; i++)
if (this.matchService.CurrentMatches[i] != null)
{
@@ -101,8 +159,8 @@ namespace Services {
MoveGemsDown();
}
public async UniTask MoveGemsDown() {
await UniTask.Delay(2);
private async UniTask MoveGemsDown() {
await UniTask.Delay(50);
int nullCounter = 0;
for (int x = 0; x < this.gameBoard.Width; x++)
@@ -127,7 +185,7 @@ namespace Services {
await FillBoard();
}
public async UniTask FillBoard() {
private async UniTask FillBoard() {
await UniTask.Delay(5);
RefillBoard();
await UniTask.Delay(5);
@@ -140,11 +198,11 @@ namespace Services {
else
{
await UniTask.Delay(5);
// currentState = GameState.Move;
this.currentState = GameState.Move;
}
}
public void RefillBoard() {
private void RefillBoard() {
for (int x = 0; x < this.gameBoard.Width; x++)
{
for (int y = 0; y < this.gameBoard.Height; y++)
@@ -152,39 +210,13 @@ namespace Services {
Gem currentGem = this.gameBoard.GetGemAt(new Vector2Int(x,y));
if (currentGem == null) {
int gemToUse = RandomUtils.RandomGemTypeAsInt();
SpawnGem(new Vector2Int(x, y), this.gameVariables.gemsPrefabs[gemToUse], (GemType)gemToUse);
SpawnGem(new Vector2Int(x, y), (GemType)gemToUse);
}
}
}
CheckMisplacedGems();
}
//Checks if there are gems that are not in the board
public void CheckMisplacedGems() {
List<GemView> gemsViews = GemsViews();
for (int x = 0; x < this.gameBoard.Width; x++)
{
for (int y = 0; y < this.gameBoard.Height; y++)
{
Gem currentGem = this.gameBoard.GetGemAt(new Vector2Int(x,y));
GemView gemView = gemsViews.FirstOrDefault(gv => gv.Gem == currentGem);
if (gemView != null) {
gemsViews.Remove(gemView);
}
}
}
foreach (GemView g in gemsViews) {
RemovePresenterFor(g);
g.Unbind();
this.objectPool.Release(g);
}
}
public void DestroyMatchedGems(Vector2Int position) {
private void DestroyMatchedGems(Vector2Int position) {
List<GemView> gemsViews = GemsViews();
Gem currentGem = this.gameBoard.GetGemAt(position);
if (currentGem != null)
@@ -194,30 +226,38 @@ namespace Services {
return;
}
//ToDo: Destroy effect
if(this.gameVariables.destroyEffectPrefabs.Length > 0)
Object.Instantiate(this.gameVariables.destroyEffectPrefabs[(int)currentGem.Type], new Vector2(position.x, position.y), Quaternion.identity);
RemovePresenterFor(gemView);
gemView.Unbind();
this.objectPool.Release(gemView);
RemovePresenterFor(gemView);
SetGem(position, null);
}
}
#region Utils
private void RemovePresenterFor(GemView gemView) {
if (gemView is null) {
return;
}
List<GemPresenter> presentersToRemove = this.gemPresenters.Where(p => p.GemView == gemView).ToList();
foreach (GemPresenter presenter in presentersToRemove) {
this.gemPresenters.Remove(presenter);
}
GemPresenter presenter = this.gemPresenters.FirstOrDefault(p => p.GemView == gemView);
this.gemPresenters.Remove(presenter);
}
private List<GemView> GemsViews() {
return this.gemsHolder.GetComponentsInChildren<GemView>().ToList();
}
private bool InBounds(Vector2Int p) {
return p.x >= 0 && p.x < this.gameBoard.Width && p.y >= 0 && p.y < this.gameBoard.Height;
}
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() {
this.objectPool.Clear();
}
}
}