Move Bomb Responsibilities to BombService
This commit is contained in:
@@ -183,7 +183,10 @@ Transform:
|
|||||||
m_LocalPosition: {x: 2.36, y: 7.7, z: 0}
|
m_LocalPosition: {x: 2.36, y: 7.7, z: 0}
|
||||||
m_LocalScale: {x: 3.09, y: 3.09, z: 3.09}
|
m_LocalScale: {x: 3.09, y: 3.09, z: 3.09}
|
||||||
m_ConstrainProportionsScale: 1
|
m_ConstrainProportionsScale: 1
|
||||||
m_Children: []
|
m_Children:
|
||||||
|
- {fileID: 638697049}
|
||||||
|
- {fileID: 544543388}
|
||||||
|
- {fileID: 1136728301}
|
||||||
m_Father: {fileID: 1879498210}
|
m_Father: {fileID: 1879498210}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!212 &96423353
|
--- !u!212 &96423353
|
||||||
@@ -356,6 +359,37 @@ Transform:
|
|||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 3199143}
|
m_Father: {fileID: 3199143}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &299575752
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 299575753}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: TileBGHolder
|
||||||
|
m_TagString: UnityObject
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &299575753
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 299575752}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 1879498210}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1 &519420028
|
--- !u!1 &519420028
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -474,11 +508,11 @@ Transform:
|
|||||||
m_GameObject: {fileID: 544543387}
|
m_GameObject: {fileID: 544543387}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
m_LocalPosition: {x: 3, y: 3, z: 0}
|
m_LocalPosition: {x: 0.20711978, y: -1.5210356, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 0.3236246, y: 0.3236246, z: 0.3236246}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 1879498210}
|
m_Father: {fileID: 96423352}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!212 &544543389
|
--- !u!212 &544543389
|
||||||
SpriteRenderer:
|
SpriteRenderer:
|
||||||
@@ -558,11 +592,11 @@ Transform:
|
|||||||
m_GameObject: {fileID: 638697048}
|
m_GameObject: {fileID: 638697048}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
m_LocalPosition: {x: 3, y: 3, z: 0}
|
m_LocalPosition: {x: 0.20711978, y: -1.5210356, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 0.3236246, y: 0.3236246, z: 0.3236246}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 1879498210}
|
m_Father: {fileID: 96423352}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!212 &638697050
|
--- !u!212 &638697050
|
||||||
SpriteRenderer:
|
SpriteRenderer:
|
||||||
@@ -915,11 +949,11 @@ Transform:
|
|||||||
m_GameObject: {fileID: 1136728300}
|
m_GameObject: {fileID: 1136728300}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
m_LocalPosition: {x: 3, y: 3, z: 0}
|
m_LocalPosition: {x: 0.20711978, y: -1.5210356, z: 0}
|
||||||
m_LocalScale: {x: 1.21, y: 1.21, z: 1.21}
|
m_LocalScale: {x: 0.3915858, y: 0.3915858, z: 0.3915858}
|
||||||
m_ConstrainProportionsScale: 1
|
m_ConstrainProportionsScale: 1
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 1879498210}
|
m_Father: {fileID: 96423352}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!212 &1136728302
|
--- !u!212 &1136728302
|
||||||
SpriteRenderer:
|
SpriteRenderer:
|
||||||
@@ -1099,12 +1133,12 @@ Transform:
|
|||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 1740955908}
|
m_GameObject: {fileID: 1740955908}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 1879498210}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1 &1879498208
|
--- !u!1 &1879498208
|
||||||
GameObject:
|
GameObject:
|
||||||
@@ -1136,9 +1170,8 @@ Transform:
|
|||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children:
|
m_Children:
|
||||||
- {fileID: 96423352}
|
- {fileID: 96423352}
|
||||||
- {fileID: 638697049}
|
- {fileID: 299575753}
|
||||||
- {fileID: 544543388}
|
- {fileID: 1740955909}
|
||||||
- {fileID: 1136728301}
|
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1 &2056718315
|
--- !u!1 &2056718315
|
||||||
@@ -1176,6 +1209,7 @@ MonoBehaviour:
|
|||||||
autoInjectGameObjects: []
|
autoInjectGameObjects: []
|
||||||
gameVariables: {fileID: 11400000, guid: edd9a973e745f4f41bce834af2c68d05, type: 2}
|
gameVariables: {fileID: 11400000, guid: edd9a973e745f4f41bce834af2c68d05, type: 2}
|
||||||
gemsHolder: {fileID: 1740955909}
|
gemsHolder: {fileID: 1740955909}
|
||||||
|
backgroundHolder: {fileID: 299575753}
|
||||||
--- !u!4 &2056718317
|
--- !u!4 &2056718317
|
||||||
Transform:
|
Transform:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -1197,7 +1231,6 @@ SceneRoots:
|
|||||||
m_Roots:
|
m_Roots:
|
||||||
- {fileID: 519420032}
|
- {fileID: 519420032}
|
||||||
- {fileID: 1879498210}
|
- {fileID: 1879498210}
|
||||||
- {fileID: 1740955909}
|
|
||||||
- {fileID: 1450061022}
|
- {fileID: 1450061022}
|
||||||
- {fileID: 3199143}
|
- {fileID: 3199143}
|
||||||
- {fileID: 2056718317}
|
- {fileID: 2056718317}
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ namespace Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Gem GetGemAt(Vector2Int pos) {
|
public Gem GetGemAt(Vector2Int pos) {
|
||||||
Gem gameObject = this.gemsGrid[pos.x, pos.y];
|
return this.gemsGrid[pos.x, pos.y];
|
||||||
return gameObject;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetGemAt(Vector2Int pos, Gem gameObject) {
|
public void SetGemAt(Vector2Int pos, Gem gameObject) {
|
||||||
|
|||||||
@@ -14,32 +14,38 @@ namespace Scopes
|
|||||||
{
|
{
|
||||||
[SerializeField] private GameVariables gameVariables;
|
[SerializeField] private GameVariables gameVariables;
|
||||||
[SerializeField] private Transform gemsHolder;
|
[SerializeField] private Transform gemsHolder;
|
||||||
|
[SerializeField] private Transform backgroundHolder;
|
||||||
|
|
||||||
protected override void Configure(IContainerBuilder builder)
|
protected override void Configure(IContainerBuilder builder)
|
||||||
{
|
{
|
||||||
|
//Register variables
|
||||||
builder.RegisterInstance(this.gameVariables);
|
builder.RegisterInstance(this.gameVariables);
|
||||||
builder.RegisterInstance(this.gemsHolder);
|
builder.RegisterInstance(this.gemsHolder);
|
||||||
|
|
||||||
|
//Register component
|
||||||
builder.RegisterComponentInHierarchy<ScoreView>();
|
builder.RegisterComponentInHierarchy<ScoreView>();
|
||||||
|
|
||||||
builder.Register<IGameBoard>(_ =>
|
builder.Register<IGameBoard>(_ =>
|
||||||
new GameBoard(this.gameVariables.width, this.gameVariables.height),
|
new GameBoard(this.gameVariables.width, this.gameVariables.height),
|
||||||
Lifetime.Scoped);
|
Lifetime.Scoped);
|
||||||
|
|
||||||
|
//Register Services
|
||||||
builder.Register<IMatchService, MatchService>(Lifetime.Scoped);
|
builder.Register<IMatchService, MatchService>(Lifetime.Scoped);
|
||||||
builder.Register<IScoreService, ScoreService>(Lifetime.Scoped);
|
builder.Register<IScoreService, ScoreService>(Lifetime.Scoped);
|
||||||
|
builder.Register<IBombService, BombService>(Lifetime.Scoped);
|
||||||
|
|
||||||
|
//Register Pool
|
||||||
builder.Register<IObjectPool<GemView>>(_ =>
|
builder.Register<IObjectPool<GemView>>(_ =>
|
||||||
new ObjectPoolService(this.gameVariables.gemsPrefabs, this.gemsHolder),
|
new ObjectPoolService(this.gameVariables.gemsPrefabs, this.gemsHolder),
|
||||||
Lifetime.Scoped);
|
Lifetime.Scoped);
|
||||||
|
|
||||||
builder.Register<IBombService, BombService>(Lifetime.Scoped);
|
//Presenters
|
||||||
|
|
||||||
builder.Register<AudioPresenter>(Lifetime.Scoped);
|
builder.Register<AudioPresenter>(Lifetime.Scoped);
|
||||||
|
|
||||||
builder.Register<ScorePresenter>(Lifetime.Scoped);
|
builder.Register<ScorePresenter>(Lifetime.Scoped);
|
||||||
|
|
||||||
builder.Register<IGameBoardService, GameBoardService>(Lifetime.Scoped).AsImplementedInterfaces();
|
builder.Register<IGameBoardService, GameBoardService>(Lifetime.Scoped).AsImplementedInterfaces();
|
||||||
|
|
||||||
|
//Entry Point
|
||||||
builder.RegisterEntryPoint<LevelEntryPoint>();
|
builder.RegisterEntryPoint<LevelEntryPoint>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ namespace ScriptableObjects {
|
|||||||
|
|
||||||
[Header("Audio")]
|
[Header("Audio")]
|
||||||
public AudioClip matchSfx;
|
public AudioClip matchSfx;
|
||||||
|
|
||||||
public AudioClip bombExplodeSfx;
|
public AudioClip bombExplodeSfx;
|
||||||
|
|
||||||
[Header("Bomb")]
|
[Header("Bomb")]
|
||||||
|
|||||||
@@ -1,45 +1,90 @@
|
|||||||
// Assets/Scripts/Services/BombService.cs
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
using Enums;
|
using Enums;
|
||||||
|
using Models.Interfaces;
|
||||||
|
using ScriptableObjects;
|
||||||
using Services.Interfaces;
|
using Services.Interfaces;
|
||||||
|
using Structs;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using Utils;
|
||||||
|
|
||||||
namespace Services
|
namespace Services
|
||||||
{
|
{
|
||||||
public class BombService : IBombService
|
public class BombService : IBombService {
|
||||||
{
|
private readonly GameVariables gameVariables;
|
||||||
public IReadOnlyList<Vector2Int> CollectTriggeredBombs(IReadOnlyList<Vector2Int> matchPositions)
|
private readonly IGameBoard gameBoard;
|
||||||
{
|
|
||||||
if (matchPositions == null || matchPositions.Count == 0)
|
private Vector2Int lastSwapFrom;
|
||||||
return Array.Empty<Vector2Int>();
|
private Vector2Int lastSwapTo;
|
||||||
|
|
||||||
|
private BombSpawnRequest? pendingBombSpawn;
|
||||||
return matchPositions.Distinct().ToList();
|
public BombSpawnRequest? PendingBombSpawn => this.pendingBombSpawn;
|
||||||
|
|
||||||
|
public void ClearPendingBombs() {
|
||||||
|
this.pendingBombSpawn = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BombService(GameVariables gameVariables, IGameBoard gameBoard) {
|
||||||
|
this.gameVariables = gameVariables;
|
||||||
|
this.gameBoard = gameBoard;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetLastSwap(Vector2Int from, Vector2Int to) {
|
||||||
|
this.lastSwapFrom = from;
|
||||||
|
this.lastSwapTo = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DetectBombSpawnFromLastSwap(List<Gem> currentMatches) {
|
||||||
|
Vector2Int from = this.lastSwapFrom;
|
||||||
|
Vector2Int to = this.lastSwapTo;
|
||||||
|
|
||||||
|
TryCreateBombSpawnAt(from, currentMatches);
|
||||||
|
TryCreateBombSpawnAt(to, currentMatches);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryCreateBombSpawnAt(Vector2Int pivot, List<Gem> currentMatches) {
|
||||||
|
Gem pivotGem = this.gameBoard.GetGemAt(pivot);
|
||||||
|
if (pivotGem == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If it's already a bomb, don't create another.
|
||||||
|
if (pivotGem.Type == GemType.Bomb)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (currentMatches.All(g => g.Position != pivot))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Only create a bomb if pivot is part of a straight 4+ line of the SAME color.
|
||||||
|
int longestLine = GetLongestMatchedLineThroughPivot(pivot, pivotGem.MatchColor);
|
||||||
|
if (longestLine < 4)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Prevent duplicates for the same cell.
|
||||||
|
if (this.pendingBombSpawn.GetValueOrDefault().Position == pivot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.pendingBombSpawn = new BombSpawnRequest(pivot, pivotGem.MatchColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async UniTask DetonateChainAsync(
|
public async UniTask DetonateChainAsync(
|
||||||
IReadOnlyList<Vector2Int> initialBombs,
|
IReadOnlyList<Vector2Int> initialBombs,
|
||||||
Func<Vector2Int, bool> inBounds,
|
|
||||||
Func<Vector2Int, Gem> getGemAt,
|
|
||||||
Func<Vector2Int, UniTask> destroyAtAsync,
|
Func<Vector2Int, UniTask> destroyAtAsync,
|
||||||
int radius,
|
IGameBoard gameBoard)
|
||||||
float bombDelaySeconds)
|
|
||||||
{
|
{
|
||||||
if (initialBombs == null || initialBombs.Count == 0)
|
if (initialBombs == null || initialBombs.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int waveDelayMs = Mathf.Max(0, Mathf.RoundToInt(bombDelaySeconds * 1000f));
|
int waveDelayMs = Mathf.RoundToInt(this.gameVariables.bombDelay * 1000f);
|
||||||
|
|
||||||
HashSet<Vector2Int> processedBombs = new HashSet<Vector2Int>();
|
HashSet<Vector2Int> processedBombs = new HashSet<Vector2Int>();
|
||||||
|
|
||||||
Queue<Vector2Int> waveQueue = new Queue<Vector2Int>(
|
Queue<Vector2Int> waveQueue = new Queue<Vector2Int>(
|
||||||
initialBombs.Where(p =>
|
initialBombs.Where(p =>
|
||||||
{
|
{
|
||||||
if (!inBounds(p)) return false;
|
if (!GemUtils.IsInBounds(p, gameBoard)) return false;
|
||||||
Gem g = getGemAt(p);
|
Gem g = gameBoard.GetGemAt(p);
|
||||||
return g is { Type: GemType.Bomb };
|
return g is { Type: GemType.Bomb };
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -54,10 +99,10 @@ namespace Services
|
|||||||
if (processedBombs.Contains(b))
|
if (processedBombs.Contains(b))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!inBounds(b))
|
if (!GemUtils.IsInBounds(b, gameBoard))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Gem g = getGemAt(b);
|
Gem g = gameBoard.GetGemAt(b);
|
||||||
if (g is not { Type: GemType.Bomb })
|
if (g is not { Type: GemType.Bomb })
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -82,15 +127,15 @@ namespace Services
|
|||||||
// destroy self when it detonates
|
// destroy self when it detonates
|
||||||
toDestroyNow.Add(bombPos);
|
toDestroyNow.Add(bombPos);
|
||||||
|
|
||||||
foreach (Vector2Int p in DiamondAreaInclusive(bombPos, radius))
|
foreach (Vector2Int p in DiamondAreaInclusive(bombPos, this.gameVariables.bombRadius))
|
||||||
{
|
{
|
||||||
if (!inBounds(p))
|
if (!GemUtils.IsInBounds(p, gameBoard))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (p == bombPos)
|
if (p == bombPos)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Gem cellGem = getGemAt(p);
|
Gem cellGem = gameBoard.GetGemAt(p);
|
||||||
if (cellGem == null)
|
if (cellGem == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -130,5 +175,31 @@ namespace Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int GetLongestMatchedLineThroughPivot(Vector2Int pivot, GemType color) {
|
||||||
|
int horizontal = 1 + CountSameColorInDirection(pivot, Vector2Int.left, color)
|
||||||
|
+ CountSameColorInDirection(pivot, Vector2Int.right, color);
|
||||||
|
|
||||||
|
int vertical = 1 + CountSameColorInDirection(pivot, Vector2Int.up, color)
|
||||||
|
+ CountSameColorInDirection(pivot, Vector2Int.down, color);
|
||||||
|
|
||||||
|
return Mathf.Max(horizontal, vertical);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int CountSameColorInDirection(Vector2Int start, Vector2Int direction, GemType color) {
|
||||||
|
int count = 0;
|
||||||
|
Vector2Int oivot = start + direction;
|
||||||
|
|
||||||
|
while (oivot.x >= 0 && oivot.x < this.gameBoard.Width && oivot.y >= 0 && oivot.y < this.gameBoard.Height) {
|
||||||
|
Gem g = this.gameBoard.GetGemAt(oivot);
|
||||||
|
if (g == null || g.Type == GemType.Bomb || g.MatchColor != color)
|
||||||
|
break;
|
||||||
|
|
||||||
|
count++;
|
||||||
|
oivot += direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,32 +17,48 @@ using Object = UnityEngine.Object;
|
|||||||
namespace Services {
|
namespace Services {
|
||||||
public class GameBoardService : IGameBoardService, ITickable, IDisposable {
|
public class GameBoardService : IGameBoardService, ITickable, IDisposable {
|
||||||
#region Inject
|
#region Inject
|
||||||
private readonly IGameBoard gameBoard;
|
|
||||||
private readonly GameVariables gameVariables;
|
private readonly GameVariables gameVariables;
|
||||||
private readonly IMatchService matchService;
|
|
||||||
private readonly IScoreService scoreService;
|
private readonly IGameBoard gameBoard;
|
||||||
private readonly AudioPresenter audioPresenter;
|
|
||||||
private readonly IBombService bombService;
|
|
||||||
private readonly IObjectPool<GemView> objectPool;
|
private readonly IObjectPool<GemView> objectPool;
|
||||||
|
|
||||||
|
private readonly IMatchService matchService;
|
||||||
|
private readonly IBombService bombService;
|
||||||
|
private readonly IScoreService scoreService;
|
||||||
|
|
||||||
|
private readonly ScorePresenter scorePresenter;
|
||||||
|
private readonly AudioPresenter audioPresenter;
|
||||||
|
|
||||||
private readonly Transform gemsHolder;
|
private readonly Transform gemsHolder;
|
||||||
|
private readonly Transform backgroundHolder;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Variables
|
#region Variables
|
||||||
private readonly List<GemPresenter> gemPresenters = new List<GemPresenter>();
|
private readonly List<GemPresenter> gemPresenters = new List<GemPresenter>();
|
||||||
private readonly ScorePresenter scorePresenter;
|
|
||||||
private GameState currentState = GameState.Move;
|
private GameState currentState = GameState.Move;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public GameBoardService(IGameBoard gameBoard, GameVariables gameVariables, IMatchService matchService, IScoreService scoreSerivce, IBombService bombService, IObjectPool<GemView> objectPool, Transform gemsHolder, ScorePresenter scorePresenter, AudioPresenter audioPresenter) {
|
public GameBoardService(
|
||||||
this.gameBoard = gameBoard;
|
GameVariables gameVariables,
|
||||||
|
IGameBoard gameBoard,
|
||||||
|
IObjectPool<GemView> objectPool,
|
||||||
|
IMatchService matchService,
|
||||||
|
IBombService bombService,
|
||||||
|
IScoreService scoreService,
|
||||||
|
ScorePresenter scorePresenter,
|
||||||
|
AudioPresenter audioPresenter,
|
||||||
|
Transform gemsHolder,
|
||||||
|
Transform backgroundHolder) {
|
||||||
this.gameVariables = gameVariables;
|
this.gameVariables = gameVariables;
|
||||||
this.matchService = matchService;
|
this.gameBoard = gameBoard;
|
||||||
this.scoreService = scoreSerivce;
|
|
||||||
this.bombService = bombService;
|
|
||||||
this.objectPool = objectPool;
|
this.objectPool = objectPool;
|
||||||
this.gemsHolder = gemsHolder;
|
this.matchService = matchService;
|
||||||
|
this.bombService = bombService;
|
||||||
|
this.scoreService = scoreService;
|
||||||
this.scorePresenter = scorePresenter;
|
this.scorePresenter = scorePresenter;
|
||||||
this.audioPresenter = audioPresenter;
|
this.audioPresenter = audioPresenter;
|
||||||
|
this.gemsHolder = gemsHolder;
|
||||||
|
this.backgroundHolder = backgroundHolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Tick() {
|
public void Tick() {
|
||||||
@@ -60,65 +76,42 @@ namespace Services {
|
|||||||
for (int y = 0; y < this.gameBoard.Height; y++)
|
for (int y = 0; y < this.gameBoard.Height; y++)
|
||||||
{
|
{
|
||||||
Vector2 position = new Vector2(x, y);
|
Vector2 position = new Vector2(x, y);
|
||||||
GameObject backgroundTile = Object.Instantiate(this.gameVariables.bgTilePrefabs, position, Quaternion.identity);
|
SpawnBackgroundTile(position);
|
||||||
backgroundTile.transform.SetParent(this.gemsHolder);
|
|
||||||
backgroundTile.name = "BG Tile - " + x + ", " + y;
|
|
||||||
|
|
||||||
int gemToUse = RandomUtils.RandomGemTypeAsInt();
|
|
||||||
|
|
||||||
int iterations = 0;
|
int iterations = 0;
|
||||||
while (this.matchService.MatchesAt(new Vector2Int(x, y), (GemType)gemToUse) && iterations < 100)
|
int gemToUse = -1;
|
||||||
{
|
do {
|
||||||
gemToUse = RandomUtils.RandomGemTypeAsInt();
|
gemToUse = RandomUtils.RandomGemTypeAsInt();
|
||||||
iterations++;
|
iterations++;
|
||||||
}
|
} while (this.matchService.MatchesAt(position.ToVector2Int(), (GemType)gemToUse) && iterations < 100);
|
||||||
|
|
||||||
SpawnGem(new Vector2Int(x, y), (GemType)gemToUse);
|
SpawnGem(position.ToVector2Int(), (GemType)gemToUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentState = GameState.Move;
|
this.currentState = GameState.Move;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SpawnBackgroundTile(Vector2 position) {
|
||||||
|
GameObject backgroundTile = Object.Instantiate(this.gameVariables.bgTilePrefabs, position, Quaternion.identity);
|
||||||
|
backgroundTile.transform.SetParent(this.backgroundHolder);
|
||||||
|
backgroundTile.name = "BG Tile - " + position.x + ", " + position.y;
|
||||||
|
}
|
||||||
|
|
||||||
//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) {
|
private void SpawnGem(Vector2Int position, GemType gemType, bool isBomb = false) {
|
||||||
GemView gemView = this.objectPool.Get(gemType, position, this.gameVariables.dropHeight);
|
if (isBomb) {
|
||||||
gemView.name = "Gem - " + position.x + ", " + position.y + ' ' + gemType;
|
DestroyMatchedGems(position);
|
||||||
|
}
|
||||||
GemTypeValues gemValue = GemUtils.GetGemValues(gemType, this.gameVariables.gemsPrefabs);
|
|
||||||
|
|
||||||
// If we randomly spawned a bomb, give it a random color group (so it can match by color).
|
GemView gemView = this.objectPool.Get(isBomb ? GemType.Bomb : gemType, position, isBomb ? 0 : this.gameVariables.dropHeight);
|
||||||
Gem gem = new Gem(gemType, position, gemValue);
|
gemView.name = "Gem - " + position.x + ", " + position.y + ' ' + gemType;
|
||||||
|
|
||||||
gemView.Bind(gem, gemValue);
|
GemTypeValues gemValue = GemUtils.GetGemValues(gemType, this.gameVariables.gemsPrefabs);
|
||||||
|
Gem gem = new Gem(isBomb ? GemType.Bomb : gemType, position, gemValue, gemType);
|
||||||
|
gemView.Bind(gem, gemValue, isBomb: isBomb);
|
||||||
|
|
||||||
this.gemPresenters.Add(new GemPresenter(gem, gemView));
|
this.gemPresenters.Add(new GemPresenter(gem, gemView));
|
||||||
SetGem(new Vector2Int(position.x, position.y), gem);
|
this.gameBoard.SetGemAt(position, gem);
|
||||||
}
|
|
||||||
|
|
||||||
private void SpawnBomb(Vector2Int position, GemType color) {
|
|
||||||
// remove existing gem/view at that position
|
|
||||||
DestroyMatchedGems(position);
|
|
||||||
|
|
||||||
GemView gemView = this.objectPool.Get(GemType.Bomb, position, 0);
|
|
||||||
gemView.name = "Bomb - " + position.x + ", " + position.y + ' ' + GemType.Bomb;
|
|
||||||
|
|
||||||
GemTypeValues gemValue = GemUtils.GetGemValues(color, this.gameVariables.gemsPrefabs);
|
|
||||||
|
|
||||||
Gem bombGem = new Gem(GemType.Bomb, position, gemValue, color);
|
|
||||||
gemView.Bind(bombGem, gemValue, isBomb: true);
|
|
||||||
|
|
||||||
this.gemPresenters.Add(new GemPresenter(bombGem, gemView));
|
|
||||||
SetGem(position, bombGem);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Sets the gem on the GameBoard
|
|
||||||
private void SetGem(Vector2Int position, Gem gem) {
|
|
||||||
this.gameBoard.SetGemAt(new Vector2Int(position.x, position.y), gem);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Gets the gem from the GameBoard
|
|
||||||
private Gem GetGem(Vector2Int position) {
|
|
||||||
return this.gameBoard.GetGemAt(position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Listens to InputService OnSwapRequest
|
//Listens to InputService OnSwapRequest
|
||||||
@@ -126,60 +119,58 @@ namespace Services {
|
|||||||
if (this.currentState != GameState.Move)
|
if (this.currentState != GameState.Move)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!InBounds(from) || !InBounds(to))
|
if (!GemUtils.IsInBounds(from, this.gameBoard) || !GemUtils.IsInBounds(to, this.gameBoard))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!AreAdjacentCardinal(from, to))
|
if (!AreAdjacentCardinal(from, to))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Gem fromGem = GetGem(from);
|
|
||||||
Gem toGem = GetGem(to);
|
|
||||||
|
|
||||||
if(fromGem == null || toGem == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
this.currentState = GameState.Wait;
|
this.currentState = GameState.Wait;
|
||||||
|
|
||||||
ApplySwap(from, to, fromGem, toGem);
|
ApplySwap(from, to);
|
||||||
|
|
||||||
await UniTask.Delay(600);
|
await UniTask.Delay(600);
|
||||||
this.matchService.SetLastSwap(from, to);
|
this.bombService.SetLastSwap(from, to);
|
||||||
|
this.bombService.ClearPendingBombs();
|
||||||
this.matchService.FindAllMatches();
|
this.matchService.FindAllMatches();
|
||||||
bool hasMatch = this.matchService.CurrentMatches.Count > 0;
|
this.bombService.DetectBombSpawnFromLastSwap(this.matchService.CurrentMatches);
|
||||||
|
|
||||||
if (!hasMatch) {
|
if (this.matchService.CurrentMatches.Count == 0) {
|
||||||
ApplySwap(to, from, fromGem, toGem);
|
ApplySwap(to, from);
|
||||||
await UniTask.Delay(600);
|
await UniTask.Delay(600);
|
||||||
this.currentState = GameState.Move;
|
this.currentState = GameState.Move;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Vector2Int> protectedPositions = ApplyPendingBombSpawns();
|
List<Vector2Int> protectedPositions = ApplyPendingBombSpawns();
|
||||||
|
|
||||||
await DestroyMatchesAsync(protectedPositions);
|
await DestroyMatchesAsync(protectedPositions);
|
||||||
this.currentState = GameState.Move;
|
this.currentState = GameState.Move;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplySwap(Vector2Int posA, Vector2Int posB, Gem gemA, Gem gemB) {
|
private void ApplySwap(Vector2Int from, Vector2Int to) {
|
||||||
|
Gem fromGem = this.gameBoard.GetGemAt(from);
|
||||||
|
Gem toGem = this.gameBoard.GetGemAt(to);
|
||||||
// swap their stored positions
|
// swap their stored positions
|
||||||
gemA.SetPosition(posB);
|
fromGem.SetPosition(to);
|
||||||
gemB.SetPosition(posA);
|
toGem.SetPosition(from);
|
||||||
|
|
||||||
// update grid
|
// update grid
|
||||||
SetGem(posA, gemB);
|
this.gameBoard.SetGemAt(from, toGem);
|
||||||
SetGem(posB, gemA);
|
this.gameBoard.SetGemAt(to, fromGem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Vector2Int> ApplyPendingBombSpawns() {
|
private List<Vector2Int> ApplyPendingBombSpawns() {
|
||||||
List<Vector2Int> positions = new List<Vector2Int>();
|
List<Vector2Int> positions = new List<Vector2Int>();
|
||||||
|
BombSpawnRequest? bombSpawnRequest = this.bombService.PendingBombSpawn;
|
||||||
foreach (BombSpawnRequest bomSpawnRequest in this.matchService.PendingBombSpawns) {
|
|
||||||
positions.Add(bomSpawnRequest.Position);
|
if (bombSpawnRequest != null) {
|
||||||
SpawnBomb(bomSpawnRequest.Position, bomSpawnRequest.Color);
|
BombSpawnRequest bombRequest = this.bombService.PendingBombSpawn.GetValueOrDefault();
|
||||||
|
positions.Add(bombRequest.Position);
|
||||||
|
SpawnGem(bombRequest.Position, bombRequest.Color, isBomb: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.matchService.ClearPendingBombs();
|
this.bombService.ClearPendingBombs();
|
||||||
return positions;
|
return positions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,16 +187,16 @@ namespace Services {
|
|||||||
matchPositions.Add(pos);
|
matchPositions.Add(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
IReadOnlyList<Vector2Int> bombCandidates = this.bombService.CollectTriggeredBombs(matchPositions);
|
IReadOnlyList<Vector2Int> bombCandidates = matchPositions.Distinct().ToList();
|
||||||
|
|
||||||
List<Vector2Int> initialBombs = new List<Vector2Int>();
|
List<Vector2Int> initialBombs = new List<Vector2Int>();
|
||||||
foreach (Vector2Int p in bombCandidates) {
|
foreach (Vector2Int p in bombCandidates) {
|
||||||
if (!InBounds(p)) continue;
|
if (!GemUtils.IsInBounds(p, this.gameBoard)) continue;
|
||||||
|
|
||||||
if (protectedPositions != null && protectedPositions.Contains(p))
|
if (protectedPositions != null && protectedPositions.Contains(p))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Gem gem = GetGem(p);
|
Gem gem = this.gameBoard.GetGemAt(p);
|
||||||
if (gem is { Type: GemType.Bomb })
|
if (gem is { Type: GemType.Bomb })
|
||||||
initialBombs.Add(p);
|
initialBombs.Add(p);
|
||||||
}
|
}
|
||||||
@@ -216,31 +207,20 @@ namespace Services {
|
|||||||
if (initialBombs.Count > 0) {
|
if (initialBombs.Count > 0) {
|
||||||
await this.bombService.DetonateChainAsync(
|
await this.bombService.DetonateChainAsync(
|
||||||
initialBombs,
|
initialBombs,
|
||||||
InBounds,
|
|
||||||
GetGem,
|
|
||||||
DestroyAtAsync,
|
DestroyAtAsync,
|
||||||
this.gameVariables.bombRadius,
|
this.gameBoard);
|
||||||
this.gameVariables.bombDelay);
|
|
||||||
|
|
||||||
await MoveGemsDown();
|
await MoveGemsDown();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool willBreakAnyNonBombGem = false;
|
bool willBreakAnyNonBombGem = matchPositions.Select(pos => this.gameBoard.GetGemAt(pos)).Where(gem => gem != null).Any(gem => gem.Type != GemType.Bomb);
|
||||||
foreach (Vector2Int pos in matchPositions) {
|
|
||||||
Gem gem = GetGem(pos);
|
|
||||||
if (gem == null) continue;
|
|
||||||
if (gem.Type == GemType.Bomb) continue;
|
|
||||||
|
|
||||||
willBreakAnyNonBombGem = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (willBreakAnyNonBombGem)
|
if (willBreakAnyNonBombGem)
|
||||||
this.audioPresenter.OnMatch(this.gameVariables.matchSfx);
|
this.audioPresenter.OnMatch(this.gameVariables.matchSfx);
|
||||||
|
|
||||||
foreach (Vector2Int pos in matchPositions.Distinct().ToList()) {
|
foreach (Vector2Int pos in matchPositions.Distinct().ToList()) {
|
||||||
Gem gem = GetGem(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;
|
||||||
|
|
||||||
@@ -250,17 +230,14 @@ namespace Services {
|
|||||||
|
|
||||||
await this.bombService.DetonateChainAsync(
|
await this.bombService.DetonateChainAsync(
|
||||||
initialBombs,
|
initialBombs,
|
||||||
InBounds,
|
|
||||||
GetGem,
|
|
||||||
DestroyAtAsync,
|
DestroyAtAsync,
|
||||||
this.gameVariables.bombRadius,
|
this.gameBoard);
|
||||||
this.gameVariables.bombDelay);
|
|
||||||
|
|
||||||
await MoveGemsDown();
|
await MoveGemsDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
private UniTask DestroyAtAsync(Vector2Int pos) {
|
private UniTask DestroyAtAsync(Vector2Int pos) {
|
||||||
Gem gem = GetGem(pos);
|
Gem gem = this.gameBoard.GetGemAt(pos);
|
||||||
if (gem == null)
|
if (gem == null)
|
||||||
return UniTask.CompletedTask;
|
return UniTask.CompletedTask;
|
||||||
|
|
||||||
@@ -288,8 +265,8 @@ namespace Services {
|
|||||||
else if (nullCounter > 0)
|
else if (nullCounter > 0)
|
||||||
{
|
{
|
||||||
currentGem.SetPosition(new Vector2Int(currentGem.Position.x, currentGem.Position.y - nullCounter));
|
currentGem.SetPosition(new Vector2Int(currentGem.Position.x, currentGem.Position.y - nullCounter));
|
||||||
SetGem(currentGem.Position, currentGem);
|
this.gameBoard.SetGemAt(currentGem.Position, currentGem);
|
||||||
SetGem(new Vector2Int(x,y), null);
|
this.gameBoard.SetGemAt(new Vector2Int(x,y), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nullCounter = 0;
|
nullCounter = 0;
|
||||||
@@ -338,7 +315,7 @@ namespace Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void DestroyMatchedGems(Vector2Int position) {
|
private void DestroyMatchedGems(Vector2Int position) {
|
||||||
List<GemView> gemsViews = GemsViews();
|
List<GemView> gemsViews = this.gemsHolder.GetComponentsInChildren<GemView>().ToList();
|
||||||
Gem currentGem = this.gameBoard.GetGemAt(position);
|
Gem currentGem = this.gameBoard.GetGemAt(position);
|
||||||
if (currentGem != null)
|
if (currentGem != null)
|
||||||
{
|
{
|
||||||
@@ -349,7 +326,7 @@ namespace Services {
|
|||||||
|
|
||||||
this.objectPool.Release(gemView);
|
this.objectPool.Release(gemView);
|
||||||
RemovePresenterFor(gemView);
|
RemovePresenterFor(gemView);
|
||||||
SetGem(position, null);
|
this.gameBoard.SetGemAt(position, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,14 +340,6 @@ namespace Services {
|
|||||||
this.gemPresenters.Remove(presenter);
|
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) {
|
private static bool AreAdjacentCardinal(Vector2Int a, Vector2Int b) {
|
||||||
Vector2Int d = b - a;
|
Vector2Int d = b - a;
|
||||||
return (Mathf.Abs(d.x) == 1 && d.y == 0) || (Mathf.Abs(d.y) == 1 && d.x == 0);
|
return (Mathf.Abs(d.x) == 1 && d.y == 0) || (Mathf.Abs(d.y) == 1 && d.x == 0);
|
||||||
|
|||||||
@@ -2,19 +2,23 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
|
using Models.Interfaces;
|
||||||
|
using Structs;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Services.Interfaces
|
namespace Services.Interfaces
|
||||||
{
|
{
|
||||||
public interface IBombService
|
public interface IBombService {
|
||||||
{
|
public BombSpawnRequest? PendingBombSpawn { get; }
|
||||||
IReadOnlyList<Vector2Int> CollectTriggeredBombs(IReadOnlyList<Vector2Int> matchPositions);
|
|
||||||
|
void SetLastSwap(Vector2Int from, Vector2Int to);
|
||||||
|
void ClearPendingBombs();
|
||||||
|
|
||||||
|
void DetectBombSpawnFromLastSwap(List<Gem> currentMatches);
|
||||||
|
|
||||||
UniTask DetonateChainAsync(
|
UniTask DetonateChainAsync(
|
||||||
IReadOnlyList<Vector2Int> initialBombs,
|
IReadOnlyList<Vector2Int> initialBombs,
|
||||||
Func<Vector2Int, bool> inBounds,
|
|
||||||
Func<Vector2Int, Gem> getGemAt,
|
|
||||||
Func<Vector2Int, UniTask> destroyAtAsync,
|
Func<Vector2Int, UniTask> destroyAtAsync,
|
||||||
int radius,
|
IGameBoard gameBoard);
|
||||||
float bombDelaySeconds);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,7 @@ using Structs;
|
|||||||
namespace Services.Interfaces {
|
namespace Services.Interfaces {
|
||||||
public interface IMatchService {
|
public interface IMatchService {
|
||||||
List<Gem> CurrentMatches { get; }
|
List<Gem> CurrentMatches { get; }
|
||||||
IReadOnlyList<BombSpawnRequest> PendingBombSpawns { get; }
|
|
||||||
bool MatchesAt(Vector2Int positionToCheck, GemType gemTypeToCheck);
|
bool MatchesAt(Vector2Int positionToCheck, GemType gemTypeToCheck);
|
||||||
void FindAllMatches();
|
void FindAllMatches();
|
||||||
void SetLastSwap(Vector2Int from, Vector2Int to);
|
|
||||||
void ClearPendingBombs();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,29 +8,15 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace Services {
|
namespace Services {
|
||||||
public class MatchService : IMatchService {
|
public class MatchService : IMatchService {
|
||||||
|
private readonly IGameBoard gameBoard;
|
||||||
|
|
||||||
|
|
||||||
private List<Gem> currentMatches = new List<Gem>();
|
private List<Gem> currentMatches = new List<Gem>();
|
||||||
public List<Gem> CurrentMatches => this.currentMatches;
|
public List<Gem> CurrentMatches => this.currentMatches;
|
||||||
|
|
||||||
private readonly List<BombSpawnRequest> pendingBombSpawns = new List<BombSpawnRequest>();
|
|
||||||
public IReadOnlyList<BombSpawnRequest> PendingBombSpawns => this.pendingBombSpawns;
|
|
||||||
|
|
||||||
private Vector2Int lastSwapFrom;
|
|
||||||
private Vector2Int lastSwapTo;
|
|
||||||
|
|
||||||
private readonly IGameBoard gameBoard;
|
|
||||||
|
|
||||||
public MatchService(IGameBoard gameBoard) {
|
public MatchService(IGameBoard gameBoard) {
|
||||||
this.gameBoard = gameBoard;
|
this.gameBoard = gameBoard;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLastSwap(Vector2Int from, Vector2Int to) {
|
|
||||||
this.lastSwapFrom = from;
|
|
||||||
this.lastSwapTo = to;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearPendingBombs() {
|
|
||||||
this.pendingBombSpawns.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool MatchesAt(Vector2Int positionToCheck, GemType gemTypeToCheck) {
|
public bool MatchesAt(Vector2Int positionToCheck, GemType gemTypeToCheck) {
|
||||||
Gem[,] gems = this.gameBoard.GemsGrid;
|
Gem[,] gems = this.gameBoard.GemsGrid;
|
||||||
@@ -66,7 +52,6 @@ namespace Services {
|
|||||||
|
|
||||||
public void FindAllMatches() {
|
public void FindAllMatches() {
|
||||||
this.currentMatches.Clear();
|
this.currentMatches.Clear();
|
||||||
this.pendingBombSpawns.Clear();
|
|
||||||
|
|
||||||
for (int x = 0; x < this.gameBoard.Width; x++)
|
for (int x = 0; x < this.gameBoard.Width; x++)
|
||||||
for (int y = 0; y < this.gameBoard.Height; y++) {
|
for (int y = 0; y < this.gameBoard.Height; y++) {
|
||||||
@@ -101,66 +86,6 @@ namespace Services {
|
|||||||
|
|
||||||
if (this.currentMatches.Count > 0)
|
if (this.currentMatches.Count > 0)
|
||||||
this.currentMatches = this.currentMatches.Distinct().ToList();
|
this.currentMatches = this.currentMatches.Distinct().ToList();
|
||||||
|
|
||||||
DetectBombSpawnFromLastSwap();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DetectBombSpawnFromLastSwap() {
|
|
||||||
Vector2Int from = this.lastSwapFrom;
|
|
||||||
Vector2Int to = this.lastSwapTo;
|
|
||||||
|
|
||||||
TryCreateBombSpawnAt(from);
|
|
||||||
TryCreateBombSpawnAt(to);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TryCreateBombSpawnAt(Vector2Int pivot) {
|
|
||||||
Gem pivotGem = this.gameBoard.GetGemAt(pivot);
|
|
||||||
if (pivotGem == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If it's already a bomb, don't create another.
|
|
||||||
if (pivotGem.Type == GemType.Bomb)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.currentMatches.All(g => g.Position != pivot))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Only create a bomb if pivot is part of a straight 4+ line of the SAME color.
|
|
||||||
int longestLine = GetLongestMatchedLineThroughPivot(pivot, pivotGem.MatchColor);
|
|
||||||
if (longestLine < 4)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Prevent duplicates for the same cell.
|
|
||||||
if (this.pendingBombSpawns.Any(b => b.Position == pivot))
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.pendingBombSpawns.Add(new BombSpawnRequest(pivot, pivotGem.MatchColor));
|
|
||||||
}
|
|
||||||
|
|
||||||
private int GetLongestMatchedLineThroughPivot(Vector2Int pivot, GemType color) {
|
|
||||||
int horizontal = 1 + CountSameColorInDirection(pivot, Vector2Int.left, color)
|
|
||||||
+ CountSameColorInDirection(pivot, Vector2Int.right, color);
|
|
||||||
|
|
||||||
int vertical = 1 + CountSameColorInDirection(pivot, Vector2Int.up, color)
|
|
||||||
+ CountSameColorInDirection(pivot, Vector2Int.down, color);
|
|
||||||
|
|
||||||
return Mathf.Max(horizontal, vertical);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int CountSameColorInDirection(Vector2Int start, Vector2Int direction, GemType color) {
|
|
||||||
int count = 0;
|
|
||||||
Vector2Int oivot = start + direction;
|
|
||||||
|
|
||||||
while (oivot.x >= 0 && oivot.x < this.gameBoard.Width && oivot.y >= 0 && oivot.y < this.gameBoard.Height) {
|
|
||||||
Gem g = this.gameBoard.GetGemAt(oivot);
|
|
||||||
if (g == null || g.Type == GemType.Bomb || g.MatchColor != color)
|
|
||||||
break;
|
|
||||||
|
|
||||||
count++;
|
|
||||||
oivot += direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
using Enums;
|
using Enums;
|
||||||
|
using Models.Interfaces;
|
||||||
|
using Services;
|
||||||
using Structs;
|
using Structs;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Utils {
|
namespace Utils {
|
||||||
public static class GemUtils {
|
public static class GemUtils {
|
||||||
@@ -10,5 +13,9 @@ namespace Utils {
|
|||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsInBounds(Vector2Int position, IGameBoard gameBoard) {
|
||||||
|
return position.x >= 0 && position.x < gameBoard.Width && position.y >= 0 && position.y < gameBoard.Height;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,5 +10,9 @@ namespace Utils {
|
|||||||
public static Vector2 ToVector2(this Vector2Int v) {
|
public static Vector2 ToVector2(this Vector2Int v) {
|
||||||
return new Vector2(v.x, v.y);
|
return new Vector2(v.x, v.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Vector2Int ToVector2Int(this Vector2 v) {
|
||||||
|
return new Vector2Int((int)v.x, (int)v.y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ namespace Views {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (Vector2.Distance(this.transform.position, positionBasedOnIndex.ToVector2()) > 0.01f) {
|
if (Vector2.Distance(this.transform.position, positionBasedOnIndex.ToVector2()) > 0.01f) {
|
||||||
this.transform.position = Vector2.Lerp(this.transform.position, positionBasedOnIndex.ToVector2(), gemSpeed);
|
this.transform.position = Vector2.Lerp(this.transform.position, positionBasedOnIndex.ToVector2(), gemSpeed * Time.deltaTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user