diff --git a/Assets/Scenes/Scene_Submission.unity b/Assets/Scenes/Scene_Submission.unity index 391619a..9963465 100644 --- a/Assets/Scenes/Scene_Submission.unity +++ b/Assets/Scenes/Scene_Submission.unity @@ -183,7 +183,10 @@ Transform: m_LocalPosition: {x: 2.36, y: 7.7, z: 0} m_LocalScale: {x: 3.09, y: 3.09, z: 3.09} m_ConstrainProportionsScale: 1 - m_Children: [] + m_Children: + - {fileID: 638697049} + - {fileID: 544543388} + - {fileID: 1136728301} m_Father: {fileID: 1879498210} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!212 &96423353 @@ -356,6 +359,37 @@ Transform: m_Children: [] m_Father: {fileID: 3199143} 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 GameObject: m_ObjectHideFlags: 0 @@ -474,11 +508,11 @@ Transform: m_GameObject: {fileID: 544543387} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 3, y: 3, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} + m_LocalPosition: {x: 0.20711978, y: -1.5210356, z: 0} + m_LocalScale: {x: 0.3236246, y: 0.3236246, z: 0.3236246} m_ConstrainProportionsScale: 0 m_Children: [] - m_Father: {fileID: 1879498210} + m_Father: {fileID: 96423352} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!212 &544543389 SpriteRenderer: @@ -558,11 +592,11 @@ Transform: m_GameObject: {fileID: 638697048} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 3, y: 3, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} + m_LocalPosition: {x: 0.20711978, y: -1.5210356, z: 0} + m_LocalScale: {x: 0.3236246, y: 0.3236246, z: 0.3236246} m_ConstrainProportionsScale: 0 m_Children: [] - m_Father: {fileID: 1879498210} + m_Father: {fileID: 96423352} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!212 &638697050 SpriteRenderer: @@ -915,11 +949,11 @@ Transform: m_GameObject: {fileID: 1136728300} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 3, y: 3, z: 0} - m_LocalScale: {x: 1.21, y: 1.21, z: 1.21} + m_LocalPosition: {x: 0.20711978, y: -1.5210356, z: 0} + m_LocalScale: {x: 0.3915858, y: 0.3915858, z: 0.3915858} m_ConstrainProportionsScale: 1 m_Children: [] - m_Father: {fileID: 1879498210} + m_Father: {fileID: 96423352} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!212 &1136728302 SpriteRenderer: @@ -1099,12 +1133,12 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1740955908} 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_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] - m_Father: {fileID: 0} + m_Father: {fileID: 1879498210} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1879498208 GameObject: @@ -1136,9 +1170,8 @@ Transform: m_ConstrainProportionsScale: 0 m_Children: - {fileID: 96423352} - - {fileID: 638697049} - - {fileID: 544543388} - - {fileID: 1136728301} + - {fileID: 299575753} + - {fileID: 1740955909} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &2056718315 @@ -1176,6 +1209,7 @@ MonoBehaviour: autoInjectGameObjects: [] gameVariables: {fileID: 11400000, guid: edd9a973e745f4f41bce834af2c68d05, type: 2} gemsHolder: {fileID: 1740955909} + backgroundHolder: {fileID: 299575753} --- !u!4 &2056718317 Transform: m_ObjectHideFlags: 0 @@ -1197,7 +1231,6 @@ SceneRoots: m_Roots: - {fileID: 519420032} - {fileID: 1879498210} - - {fileID: 1740955909} - {fileID: 1450061022} - {fileID: 3199143} - {fileID: 2056718317} diff --git a/Assets/Scripts/Models/GameBoard.cs b/Assets/Scripts/Models/GameBoard.cs index a8dc6d8..e362ec0 100644 --- a/Assets/Scripts/Models/GameBoard.cs +++ b/Assets/Scripts/Models/GameBoard.cs @@ -16,8 +16,7 @@ namespace Services { } public Gem GetGemAt(Vector2Int pos) { - Gem gameObject = this.gemsGrid[pos.x, pos.y]; - return gameObject; + return this.gemsGrid[pos.x, pos.y]; } public void SetGemAt(Vector2Int pos, Gem gameObject) { diff --git a/Assets/Scripts/Scopes/LevelLifetimeScope.cs b/Assets/Scripts/Scopes/LevelLifetimeScope.cs index 0817f5f..7283550 100644 --- a/Assets/Scripts/Scopes/LevelLifetimeScope.cs +++ b/Assets/Scripts/Scopes/LevelLifetimeScope.cs @@ -14,32 +14,38 @@ namespace Scopes { [SerializeField] private GameVariables gameVariables; [SerializeField] private Transform gemsHolder; + [SerializeField] private Transform backgroundHolder; protected override void Configure(IContainerBuilder builder) { + //Register variables builder.RegisterInstance(this.gameVariables); builder.RegisterInstance(this.gemsHolder); + //Register component builder.RegisterComponentInHierarchy(); builder.Register(_ => new GameBoard(this.gameVariables.width, this.gameVariables.height), Lifetime.Scoped); + //Register Services builder.Register(Lifetime.Scoped); builder.Register(Lifetime.Scoped); + builder.Register(Lifetime.Scoped); + //Register Pool builder.Register>(_ => new ObjectPoolService(this.gameVariables.gemsPrefabs, this.gemsHolder), Lifetime.Scoped); - builder.Register(Lifetime.Scoped); - + //Presenters builder.Register(Lifetime.Scoped); - builder.Register(Lifetime.Scoped); + builder.Register(Lifetime.Scoped).AsImplementedInterfaces(); + //Entry Point builder.RegisterEntryPoint(); } } diff --git a/Assets/Scripts/ScriptableObjects/GameVariables.cs b/Assets/Scripts/ScriptableObjects/GameVariables.cs index 3d04031..5a1986b 100644 --- a/Assets/Scripts/ScriptableObjects/GameVariables.cs +++ b/Assets/Scripts/ScriptableObjects/GameVariables.cs @@ -14,7 +14,6 @@ namespace ScriptableObjects { [Header("Audio")] public AudioClip matchSfx; - public AudioClip bombExplodeSfx; [Header("Bomb")] diff --git a/Assets/Scripts/Services/BombService.cs b/Assets/Scripts/Services/BombService.cs index ed7012d..b48ff26 100644 --- a/Assets/Scripts/Services/BombService.cs +++ b/Assets/Scripts/Services/BombService.cs @@ -1,45 +1,90 @@ -// Assets/Scripts/Services/BombService.cs using System; using System.Collections.Generic; using System.Linq; using Cysharp.Threading.Tasks; using Enums; +using Models.Interfaces; +using ScriptableObjects; using Services.Interfaces; +using Structs; using UnityEngine; +using Utils; namespace Services { - public class BombService : IBombService - { - public IReadOnlyList CollectTriggeredBombs(IReadOnlyList matchPositions) - { - if (matchPositions == null || matchPositions.Count == 0) - return Array.Empty(); - - - return matchPositions.Distinct().ToList(); + public class BombService : IBombService { + private readonly GameVariables gameVariables; + private readonly IGameBoard gameBoard; + + private Vector2Int lastSwapFrom; + private Vector2Int lastSwapTo; + + private BombSpawnRequest? pendingBombSpawn; + 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 currentMatches) { + Vector2Int from = this.lastSwapFrom; + Vector2Int to = this.lastSwapTo; + + TryCreateBombSpawnAt(from, currentMatches); + TryCreateBombSpawnAt(to, currentMatches); + } + + private void TryCreateBombSpawnAt(Vector2Int pivot, List 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( IReadOnlyList initialBombs, - Func inBounds, - Func getGemAt, Func destroyAtAsync, - int radius, - float bombDelaySeconds) + IGameBoard gameBoard) { if (initialBombs == null || initialBombs.Count == 0) return; - int waveDelayMs = Mathf.Max(0, Mathf.RoundToInt(bombDelaySeconds * 1000f)); + int waveDelayMs = Mathf.RoundToInt(this.gameVariables.bombDelay * 1000f); HashSet processedBombs = new HashSet(); Queue waveQueue = new Queue( initialBombs.Where(p => { - if (!inBounds(p)) return false; - Gem g = getGemAt(p); + if (!GemUtils.IsInBounds(p, gameBoard)) return false; + Gem g = gameBoard.GetGemAt(p); return g is { Type: GemType.Bomb }; }) ); @@ -54,10 +99,10 @@ namespace Services if (processedBombs.Contains(b)) continue; - if (!inBounds(b)) + if (!GemUtils.IsInBounds(b, gameBoard)) continue; - Gem g = getGemAt(b); + Gem g = gameBoard.GetGemAt(b); if (g is not { Type: GemType.Bomb }) continue; @@ -82,15 +127,15 @@ namespace Services // destroy self when it detonates 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; if (p == bombPos) continue; - Gem cellGem = getGemAt(p); + Gem cellGem = gameBoard.GetGemAt(p); if (cellGem == null) 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; + } } } \ No newline at end of file diff --git a/Assets/Scripts/Services/GameBoardService.cs b/Assets/Scripts/Services/GameBoardService.cs index 0d7fa27..8ab9bfd 100644 --- a/Assets/Scripts/Services/GameBoardService.cs +++ b/Assets/Scripts/Services/GameBoardService.cs @@ -17,32 +17,48 @@ using Object = UnityEngine.Object; namespace Services { 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 AudioPresenter audioPresenter; - private readonly IBombService bombService; + + private readonly IGameBoard gameBoard; private readonly IObjectPool 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 backgroundHolder; #endregion #region Variables private readonly List gemPresenters = new List(); - private readonly ScorePresenter scorePresenter; private GameState currentState = GameState.Move; #endregion - public GameBoardService(IGameBoard gameBoard, GameVariables gameVariables, IMatchService matchService, IScoreService scoreSerivce, IBombService bombService, IObjectPool objectPool, Transform gemsHolder, ScorePresenter scorePresenter, AudioPresenter audioPresenter) { - this.gameBoard = gameBoard; + public GameBoardService( + GameVariables gameVariables, + IGameBoard gameBoard, + IObjectPool objectPool, + IMatchService matchService, + IBombService bombService, + IScoreService scoreService, + ScorePresenter scorePresenter, + AudioPresenter audioPresenter, + Transform gemsHolder, + Transform backgroundHolder) { this.gameVariables = gameVariables; - this.matchService = matchService; - this.scoreService = scoreSerivce; - this.bombService = bombService; + this.gameBoard = gameBoard; this.objectPool = objectPool; - this.gemsHolder = gemsHolder; + this.matchService = matchService; + this.bombService = bombService; + this.scoreService = scoreService; this.scorePresenter = scorePresenter; this.audioPresenter = audioPresenter; + this.gemsHolder = gemsHolder; + this.backgroundHolder = backgroundHolder; } public void Tick() { @@ -60,65 +76,42 @@ namespace Services { for (int y = 0; y < this.gameBoard.Height; y++) { Vector2 position = new Vector2(x, y); - GameObject backgroundTile = Object.Instantiate(this.gameVariables.bgTilePrefabs, position, Quaternion.identity); - backgroundTile.transform.SetParent(this.gemsHolder); - backgroundTile.name = "BG Tile - " + x + ", " + y; - - int gemToUse = RandomUtils.RandomGemTypeAsInt(); + SpawnBackgroundTile(position); int iterations = 0; - while (this.matchService.MatchesAt(new Vector2Int(x, y), (GemType)gemToUse) && iterations < 100) - { + int gemToUse = -1; + do { gemToUse = RandomUtils.RandomGemTypeAsInt(); 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; } + 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 - private void SpawnGem(Vector2Int position, GemType gemType) { - GemView gemView = this.objectPool.Get(gemType, position, this.gameVariables.dropHeight); - gemView.name = "Gem - " + position.x + ", " + position.y + ' ' + gemType; - - GemTypeValues gemValue = GemUtils.GetGemValues(gemType, this.gameVariables.gemsPrefabs); + private void SpawnGem(Vector2Int position, GemType gemType, bool isBomb = false) { + if (isBomb) { + DestroyMatchedGems(position); + } - // If we randomly spawned a bomb, give it a random color group (so it can match by color). - Gem gem = new Gem(gemType, position, gemValue); - - gemView.Bind(gem, gemValue); + GemView gemView = this.objectPool.Get(isBomb ? GemType.Bomb : gemType, position, isBomb ? 0 : this.gameVariables.dropHeight); + gemView.name = "Gem - " + position.x + ", " + position.y + ' ' + gemType; + + 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)); - SetGem(new Vector2Int(position.x, position.y), 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); + this.gameBoard.SetGemAt(position, gem); } //Listens to InputService OnSwapRequest @@ -126,60 +119,58 @@ namespace Services { if (this.currentState != GameState.Move) return false; - if (!InBounds(from) || !InBounds(to)) + if (!GemUtils.IsInBounds(from, this.gameBoard) || !GemUtils.IsInBounds(to, this.gameBoard)) 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); + ApplySwap(from, to); await UniTask.Delay(600); - this.matchService.SetLastSwap(from, to); + this.bombService.SetLastSwap(from, to); + this.bombService.ClearPendingBombs(); this.matchService.FindAllMatches(); - bool hasMatch = this.matchService.CurrentMatches.Count > 0; - - if (!hasMatch) { - ApplySwap(to, from, fromGem, toGem); + this.bombService.DetectBombSpawnFromLastSwap(this.matchService.CurrentMatches); + + if (this.matchService.CurrentMatches.Count == 0) { + ApplySwap(to, from); await UniTask.Delay(600); this.currentState = GameState.Move; return false; } List protectedPositions = ApplyPendingBombSpawns(); - await DestroyMatchesAsync(protectedPositions); this.currentState = GameState.Move; 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 - gemA.SetPosition(posB); - gemB.SetPosition(posA); + fromGem.SetPosition(to); + toGem.SetPosition(from); // update grid - SetGem(posA, gemB); - SetGem(posB, gemA); + this.gameBoard.SetGemAt(from, toGem); + this.gameBoard.SetGemAt(to, fromGem); } private List ApplyPendingBombSpawns() { List positions = new List(); - - foreach (BombSpawnRequest bomSpawnRequest in this.matchService.PendingBombSpawns) { - positions.Add(bomSpawnRequest.Position); - SpawnBomb(bomSpawnRequest.Position, bomSpawnRequest.Color); + 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.matchService.ClearPendingBombs(); + this.bombService.ClearPendingBombs(); return positions; } @@ -196,16 +187,16 @@ namespace Services { matchPositions.Add(pos); } - IReadOnlyList bombCandidates = this.bombService.CollectTriggeredBombs(matchPositions); + IReadOnlyList bombCandidates = matchPositions.Distinct().ToList(); List initialBombs = new List(); foreach (Vector2Int p in bombCandidates) { - if (!InBounds(p)) continue; + if (!GemUtils.IsInBounds(p, this.gameBoard)) continue; if (protectedPositions != null && protectedPositions.Contains(p)) continue; - Gem gem = GetGem(p); + Gem gem = this.gameBoard.GetGemAt(p); if (gem is { Type: GemType.Bomb }) initialBombs.Add(p); } @@ -216,31 +207,20 @@ namespace Services { if (initialBombs.Count > 0) { await this.bombService.DetonateChainAsync( initialBombs, - InBounds, - GetGem, DestroyAtAsync, - this.gameVariables.bombRadius, - this.gameVariables.bombDelay); + this.gameBoard); await MoveGemsDown(); return; } - bool willBreakAnyNonBombGem = false; - foreach (Vector2Int pos in matchPositions) { - Gem gem = GetGem(pos); - if (gem == null) continue; - if (gem.Type == GemType.Bomb) continue; - - willBreakAnyNonBombGem = true; - break; - } + 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); foreach (Vector2Int pos in matchPositions.Distinct().ToList()) { - Gem gem = GetGem(pos); + Gem gem = this.gameBoard.GetGemAt(pos); if (gem == null) continue; if (gem.Type == GemType.Bomb) continue; @@ -250,17 +230,14 @@ namespace Services { await this.bombService.DetonateChainAsync( initialBombs, - InBounds, - GetGem, DestroyAtAsync, - this.gameVariables.bombRadius, - this.gameVariables.bombDelay); + this.gameBoard); await MoveGemsDown(); } private UniTask DestroyAtAsync(Vector2Int pos) { - Gem gem = GetGem(pos); + Gem gem = this.gameBoard.GetGemAt(pos); if (gem == null) return UniTask.CompletedTask; @@ -288,8 +265,8 @@ namespace Services { else if (nullCounter > 0) { currentGem.SetPosition(new Vector2Int(currentGem.Position.x, currentGem.Position.y - nullCounter)); - SetGem(currentGem.Position, currentGem); - SetGem(new Vector2Int(x,y), null); + this.gameBoard.SetGemAt(currentGem.Position, currentGem); + this.gameBoard.SetGemAt(new Vector2Int(x,y), null); } } nullCounter = 0; @@ -338,7 +315,7 @@ namespace Services { } private void DestroyMatchedGems(Vector2Int position) { - List gemsViews = GemsViews(); + List gemsViews = this.gemsHolder.GetComponentsInChildren().ToList(); Gem currentGem = this.gameBoard.GetGemAt(position); if (currentGem != null) { @@ -349,7 +326,7 @@ namespace Services { this.objectPool.Release(gemView); RemovePresenterFor(gemView); - SetGem(position, null); + this.gameBoard.SetGemAt(position, null); } } @@ -363,14 +340,6 @@ namespace Services { this.gemPresenters.Remove(presenter); } - private List GemsViews() { - return this.gemsHolder.GetComponentsInChildren().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); diff --git a/Assets/Scripts/Services/Interfaces/IBombService.cs b/Assets/Scripts/Services/Interfaces/IBombService.cs index 19e4ed6..ec520a9 100644 --- a/Assets/Scripts/Services/Interfaces/IBombService.cs +++ b/Assets/Scripts/Services/Interfaces/IBombService.cs @@ -2,19 +2,23 @@ using System; using System.Collections.Generic; using Cysharp.Threading.Tasks; +using Models.Interfaces; +using Structs; using UnityEngine; namespace Services.Interfaces { - public interface IBombService - { - IReadOnlyList CollectTriggeredBombs(IReadOnlyList matchPositions); + public interface IBombService { + public BombSpawnRequest? PendingBombSpawn { get; } + + void SetLastSwap(Vector2Int from, Vector2Int to); + void ClearPendingBombs(); + + void DetectBombSpawnFromLastSwap(List currentMatches); + UniTask DetonateChainAsync( IReadOnlyList initialBombs, - Func inBounds, - Func getGemAt, Func destroyAtAsync, - int radius, - float bombDelaySeconds); + IGameBoard gameBoard); } } \ No newline at end of file diff --git a/Assets/Scripts/Services/Interfaces/IMatchService.cs b/Assets/Scripts/Services/Interfaces/IMatchService.cs index 9206c2a..d6841d4 100644 --- a/Assets/Scripts/Services/Interfaces/IMatchService.cs +++ b/Assets/Scripts/Services/Interfaces/IMatchService.cs @@ -6,10 +6,7 @@ using Structs; namespace Services.Interfaces { public interface IMatchService { List CurrentMatches { get; } - IReadOnlyList PendingBombSpawns { get; } bool MatchesAt(Vector2Int positionToCheck, GemType gemTypeToCheck); void FindAllMatches(); - void SetLastSwap(Vector2Int from, Vector2Int to); - void ClearPendingBombs(); } } \ No newline at end of file diff --git a/Assets/Scripts/Services/MatchService.cs b/Assets/Scripts/Services/MatchService.cs index f45f738..8cd14c3 100644 --- a/Assets/Scripts/Services/MatchService.cs +++ b/Assets/Scripts/Services/MatchService.cs @@ -8,29 +8,15 @@ using UnityEngine; namespace Services { public class MatchService : IMatchService { + private readonly IGameBoard gameBoard; + + private List currentMatches = new List(); public List CurrentMatches => this.currentMatches; - - private readonly List pendingBombSpawns = new List(); - public IReadOnlyList PendingBombSpawns => this.pendingBombSpawns; - - private Vector2Int lastSwapFrom; - private Vector2Int lastSwapTo; - private readonly IGameBoard gameBoard; - public MatchService(IGameBoard 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) { Gem[,] gems = this.gameBoard.GemsGrid; @@ -66,7 +52,6 @@ namespace Services { public void FindAllMatches() { this.currentMatches.Clear(); - this.pendingBombSpawns.Clear(); for (int x = 0; x < this.gameBoard.Width; x++) for (int y = 0; y < this.gameBoard.Height; y++) { @@ -101,66 +86,6 @@ namespace Services { if (this.currentMatches.Count > 0) 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; } } } \ No newline at end of file diff --git a/Assets/Scripts/Utils/GemUtils.cs b/Assets/Scripts/Utils/GemUtils.cs index e5ad539..474b850 100644 --- a/Assets/Scripts/Utils/GemUtils.cs +++ b/Assets/Scripts/Utils/GemUtils.cs @@ -1,5 +1,8 @@ using Enums; +using Models.Interfaces; +using Services; using Structs; +using UnityEngine; namespace Utils { public static class GemUtils { @@ -10,5 +13,9 @@ namespace Utils { 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; + } } } \ No newline at end of file diff --git a/Assets/Scripts/Utils/Vector2IntUtils.cs b/Assets/Scripts/Utils/Vector2IntUtils.cs index cfa9c8e..d843470 100644 --- a/Assets/Scripts/Utils/Vector2IntUtils.cs +++ b/Assets/Scripts/Utils/Vector2IntUtils.cs @@ -10,5 +10,9 @@ namespace Utils { public static Vector2 ToVector2(this Vector2Int v) { return new Vector2(v.x, v.y); } + + public static Vector2Int ToVector2Int(this Vector2 v) { + return new Vector2Int((int)v.x, (int)v.y); + } } } \ No newline at end of file diff --git a/Assets/Scripts/Views/GemView.cs b/Assets/Scripts/Views/GemView.cs index 18adfca..34da557 100644 --- a/Assets/Scripts/Views/GemView.cs +++ b/Assets/Scripts/Views/GemView.cs @@ -100,7 +100,7 @@ namespace Views { return; 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); } } }