Cleanup
This commit is contained in:
@@ -51,12 +51,11 @@ MonoBehaviour:
|
|||||||
explosionPrefab: {fileID: 8968486364681163996, guid: 05c754e3d4f9fd349ac1def58d17670f,
|
explosionPrefab: {fileID: 8968486364681163996, guid: 05c754e3d4f9fd349ac1def58d17670f,
|
||||||
type: 3}
|
type: 3}
|
||||||
scoreValue: 10
|
scoreValue: 10
|
||||||
bonusAmount: 0.5
|
bombDelay: 0.75
|
||||||
bombChance: 2
|
bombSelfDelay: 0.25
|
||||||
|
bombRadius: 2
|
||||||
dropHeight: 1
|
dropHeight: 1
|
||||||
gemSpeed: 7
|
gemSpeed: 0.05
|
||||||
scoreSpeed: 5
|
scoreSpeed: 1
|
||||||
width: 7
|
width: 7
|
||||||
height: 7
|
height: 7
|
||||||
rowsSize: 7
|
|
||||||
colsSize: 7
|
|
||||||
|
|||||||
@@ -5,19 +5,22 @@ namespace Services {
|
|||||||
public class Gem {
|
public class Gem {
|
||||||
private GemType type;
|
private GemType type;
|
||||||
private Vector2Int position;
|
private Vector2Int position;
|
||||||
|
|
||||||
public GemType Type => this.type;
|
public GemType Type => this.type;
|
||||||
public Vector2Int Position => this.position;
|
public Vector2Int Position => this.position;
|
||||||
|
|
||||||
private int scoreValue;
|
private int scoreValue;
|
||||||
public int ScoreValue => this.scoreValue;
|
public int ScoreValue => this.scoreValue;
|
||||||
|
|
||||||
public bool isMatch = false;
|
private GemType colorType;
|
||||||
|
|
||||||
public Gem(GemType type, Vector2Int position, int scoreValue) {
|
public GemType MatchColor => this.type == GemType.Bomb ? this.colorType : this.type;
|
||||||
|
|
||||||
|
public Gem(GemType type, Vector2Int position, int scoreValue, GemType? colorType = null) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.scoreValue = scoreValue;
|
this.scoreValue = scoreValue;
|
||||||
|
|
||||||
|
this.colorType = colorType ?? type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPosition(Vector2Int position) {
|
public void SetPosition(Vector2Int position) {
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ namespace Presenter {
|
|||||||
this.gemView = gemView;
|
this.gemView = gemView;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Tick() {
|
public void Tick(float gemSpeed) {
|
||||||
if (this.gemView == null) {
|
if (this.gemView == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.gem.Position.Compare(this.gemView.transform.localPosition)) {
|
if (!this.gem.Position.Compare(this.gemView.transform.localPosition)) {
|
||||||
this.gemView.UpdatePosition(this.gem.Position);
|
this.gemView.UpdatePosition(this.gem.Position, gemSpeed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ namespace Presenter {
|
|||||||
this.scoreView.SetScore(this.scoreService.Score);
|
this.scoreView.SetScore(this.scoreService.Score);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Tick() {
|
public void Tick(float scoreSpeed) {
|
||||||
this.scoreView.UpdateScore();
|
this.scoreView.UpdateScore(scoreSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnScoreChanged(int score) {
|
private void OnScoreChanged(int score) {
|
||||||
|
|||||||
@@ -5,23 +5,18 @@ using Structs;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Views;
|
using Views;
|
||||||
|
|
||||||
//Done, moved to GameVariables scriptable object
|
|
||||||
namespace ScriptableObjects {
|
namespace ScriptableObjects {
|
||||||
[CreateAssetMenu(fileName = "GameVariables", menuName = "Game Variables")]
|
[CreateAssetMenu(fileName = "GameVariables", menuName = "Game Variables")]
|
||||||
public class GameVariables : ScriptableObject {
|
public class GameVariables : ScriptableObject {
|
||||||
public GameObject bgTilePrefabs;
|
public GameObject bgTilePrefabs;
|
||||||
public GemTypeValues[] gemsPrefabs;
|
public GemTypeValues[] gemsPrefabs;
|
||||||
public float bonusAmount = 0.5f;
|
public float bombDelay = 0.1f;
|
||||||
public float bombChance = 2f;
|
public float bombSelfDelay = 0.05f;
|
||||||
|
public int bombRadius = 1;
|
||||||
public int dropHeight = 1;
|
public int dropHeight = 1;
|
||||||
public float gemSpeed = 0.1f;
|
public float gemSpeed = 0.1f;
|
||||||
public float scoreSpeed = 5;
|
public float scoreSpeed = 5;
|
||||||
public int width;
|
public int width;
|
||||||
public int height;
|
public int height;
|
||||||
|
|
||||||
[HideInInspector]
|
|
||||||
public int rowsSize = 7;
|
|
||||||
[HideInInspector]
|
|
||||||
public int colsSize = 7;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ using Models.Interfaces;
|
|||||||
using Presenter;
|
using Presenter;
|
||||||
using ScriptableObjects;
|
using ScriptableObjects;
|
||||||
using Services.Interfaces;
|
using Services.Interfaces;
|
||||||
|
using Structs;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Utils;
|
using Utils;
|
||||||
using VContainer.Unity;
|
using VContainer.Unity;
|
||||||
@@ -43,10 +44,10 @@ namespace Services {
|
|||||||
|
|
||||||
public void Tick() {
|
public void Tick() {
|
||||||
foreach (GemPresenter gemPresenter in gemPresenters) {
|
foreach (GemPresenter gemPresenter in gemPresenters) {
|
||||||
gemPresenter.Tick();
|
gemPresenter.Tick(this.gameVariables.gemSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scorePresenter.Tick();
|
this.scorePresenter.Tick(this.gameVariables.scoreSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Instantiates background tiles and calls SpawnGems
|
//Instantiates background tiles and calls SpawnGems
|
||||||
@@ -77,18 +78,34 @@ 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) {
|
private void SpawnGem(Vector2Int position, GemType gemType) {
|
||||||
if (Random.Range(0, 100f) < this.gameVariables.bombChance)
|
|
||||||
gemType = GemType.Bomb;
|
|
||||||
|
|
||||||
GemView gemView = this.objectPool.Get(gemType, position, this.gameVariables.dropHeight);
|
GemView gemView = this.objectPool.Get(gemType, position, this.gameVariables.dropHeight);
|
||||||
gemView.name = "Gem - " + position.x + ", " + position.y + ' ' + gemType;
|
gemView.name = "Gem - " + position.x + ", " + position.y + ' ' + gemType;
|
||||||
Gem gem = new Gem(gemType, position, 50);
|
|
||||||
|
// If we randomly spawned a bomb, give it a random color group (so it can match by color).
|
||||||
|
int scoreValue = GemUtils.GetGemValues(gemType, this.gameVariables.gemsPrefabs).scoreValue;
|
||||||
|
Gem gem = new Gem(gemType, position, scoreValue);
|
||||||
|
|
||||||
gemView.Bind(gem);
|
gemView.Bind(gem);
|
||||||
|
|
||||||
this.gemPresenters.Add(new GemPresenter(gem, gemView));
|
this.gemPresenters.Add(new GemPresenter(gem, gemView));
|
||||||
SetGem(new Vector2Int(position.x, position.y), gem);
|
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, this.gameVariables.dropHeight);
|
||||||
|
gemView.name = "Gem - " + position.x + ", " + position.y + ' ' + GemType.Bomb;
|
||||||
|
|
||||||
|
int scoreValue = GemUtils.GetGemValues(color, this.gameVariables.gemsPrefabs).scoreValue;
|
||||||
|
Gem bombGem = new Gem(GemType.Bomb, position, scoreValue, color);
|
||||||
|
gemView.Bind(bombGem);
|
||||||
|
|
||||||
|
this.gemPresenters.Add(new GemPresenter(bombGem, gemView));
|
||||||
|
SetGem(position, bombGem);
|
||||||
|
}
|
||||||
|
|
||||||
//Sets the gem on the GameBoard
|
//Sets the gem on the GameBoard
|
||||||
private void SetGem(Vector2Int position, Gem gem) {
|
private void SetGem(Vector2Int position, Gem gem) {
|
||||||
this.gameBoard.SetGemAt(new Vector2Int(position.x, position.y), gem);
|
this.gameBoard.SetGemAt(new Vector2Int(position.x, position.y), gem);
|
||||||
@@ -121,7 +138,7 @@ namespace Services {
|
|||||||
ApplySwap(from, to, fromGem, toGem);
|
ApplySwap(from, to, fromGem, toGem);
|
||||||
|
|
||||||
await UniTask.Delay(600);
|
await UniTask.Delay(600);
|
||||||
|
this.matchService.SetLastSwap(from, to);
|
||||||
this.matchService.FindAllMatches();
|
this.matchService.FindAllMatches();
|
||||||
bool hasMatch = this.matchService.CurrentMatches.Count > 0;
|
bool hasMatch = this.matchService.CurrentMatches.Count > 0;
|
||||||
|
|
||||||
@@ -132,7 +149,9 @@ namespace Services {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DestroyMatches();
|
List<Vector2Int> protectedPositions = ApplyPendingBombSpawns();
|
||||||
|
|
||||||
|
await DestroyMatchesAsync(protectedPositions);
|
||||||
this.currentState = GameState.Move;
|
this.currentState = GameState.Move;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -147,16 +166,130 @@ namespace Services {
|
|||||||
SetGem(posB, gemA);
|
SetGem(posB, gemA);
|
||||||
}
|
}
|
||||||
|
|
||||||
//If there are matches, destroys them and moves the gems down
|
private List<Vector2Int> ApplyPendingBombSpawns() {
|
||||||
private void DestroyMatches() {
|
List<Vector2Int> positions = new List<Vector2Int>();
|
||||||
for (int i = 0; i < this.matchService.CurrentMatches.Count; i++)
|
|
||||||
if (this.matchService.CurrentMatches[i] != null)
|
foreach (BombSpawnRequest bomSpawnRequest in this.matchService.PendingBombSpawns) {
|
||||||
{
|
positions.Add(bomSpawnRequest.Position);
|
||||||
this.scoreService.ScoreCheck(this.matchService.CurrentMatches[i].ScoreValue);
|
SpawnBomb(bomSpawnRequest.Position, bomSpawnRequest.Color);
|
||||||
DestroyMatchedGems(this.matchService.CurrentMatches[i].Position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MoveGemsDown();
|
this.matchService.ClearPendingBombs();
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTask DestroyMatchesAsync(List<Vector2Int> protectedPositions) {
|
||||||
|
// Build initial queues from current matches
|
||||||
|
Queue<Vector2Int> bombsToProcess = new Queue<Vector2Int>();
|
||||||
|
List<Vector2Int> processedBombs = new List<Vector2Int>();
|
||||||
|
List<Vector2Int> regularToDestroy = new List<Vector2Int>();
|
||||||
|
|
||||||
|
for (int i = 0; i < this.matchService.CurrentMatches.Count; i++) {
|
||||||
|
Gem matchedGem = this.matchService.CurrentMatches[i];
|
||||||
|
if (matchedGem == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Vector2Int pos = matchedGem.Position;
|
||||||
|
|
||||||
|
// If a bomb was spawned at this cell due to 4+ creation, it must survive this destruction pass.
|
||||||
|
if (protectedPositions != null && protectedPositions.Contains(pos))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Gem current = GetGem(pos);
|
||||||
|
if (current == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (current.Type == GemType.Bomb) {
|
||||||
|
bombsToProcess.Enqueue(pos);
|
||||||
|
} else {
|
||||||
|
regularToDestroy.Add(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process bombs: neighbors first (after delay), then the bomb itself (after delay).
|
||||||
|
while (bombsToProcess.Count > 0) {
|
||||||
|
Vector2Int bombPos = bombsToProcess.Dequeue();
|
||||||
|
if (processedBombs.Contains(bombPos))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Gem bomb = GetGem(bombPos);
|
||||||
|
if (bomb is not { Type: GemType.Bomb })
|
||||||
|
continue;
|
||||||
|
|
||||||
|
processedBombs.Add(bombPos);
|
||||||
|
|
||||||
|
// Delay before destroying neighbor group
|
||||||
|
if (this.gameVariables.bombDelay > 0f) {
|
||||||
|
int msDelay = Mathf.RoundToInt(this.gameVariables.bombDelay * 1000f);
|
||||||
|
await UniTask.Delay(msDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect cross neighbors
|
||||||
|
foreach (Vector2Int neighborPosition in CrossNeighbors(bombPos, this.gameVariables.bombRadius)) {
|
||||||
|
if (!InBounds(neighborPosition))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Gem g = GetGem(neighborPosition);
|
||||||
|
if (g == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If we encounter another bomb, queue it (so it explodes too).
|
||||||
|
if (g.Type == GemType.Bomb) {
|
||||||
|
if (!processedBombs.Contains(neighborPosition))
|
||||||
|
bombsToProcess.Enqueue(neighborPosition);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
regularToDestroy.Add(neighborPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the neighbor group now
|
||||||
|
foreach (Vector2Int position in regularToDestroy.ToList()) {
|
||||||
|
Gem gem = GetGem(position);
|
||||||
|
if (gem == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
this.scoreService.ScoreCheck(gem.ScoreValue);
|
||||||
|
DestroyMatchedGems(position);
|
||||||
|
}
|
||||||
|
regularToDestroy.Clear();
|
||||||
|
|
||||||
|
// Delay before destroying the bomb itself
|
||||||
|
if (this.gameVariables.bombSelfDelay > 0f) {
|
||||||
|
int ms = Mathf.RoundToInt(this.gameVariables.bombSelfDelay * 1000f);
|
||||||
|
await UniTask.Delay(ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the bomb
|
||||||
|
Gem b = GetGem(bombPos);
|
||||||
|
if (b != null && b.Type == GemType.Bomb) {
|
||||||
|
this.scoreService.ScoreCheck(b.ScoreValue);
|
||||||
|
DestroyMatchedGems(bombPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy any remaining regular matches (non-bomb) after bomb processing
|
||||||
|
foreach (Vector2Int pos in regularToDestroy) {
|
||||||
|
Gem g = GetGem(pos);
|
||||||
|
if (g == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
this.scoreService.ScoreCheck(g.ScoreValue);
|
||||||
|
DestroyMatchedGems(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we can cascade
|
||||||
|
await MoveGemsDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Vector2Int> CrossNeighbors(Vector2Int center, int radius) {
|
||||||
|
// center excluded for "neighbors first"
|
||||||
|
for (int i = 1; i <= radius; i++) {
|
||||||
|
yield return center + Vector2Int.left * i;
|
||||||
|
yield return center + Vector2Int.right * i;
|
||||||
|
yield return center + Vector2Int.up * i;
|
||||||
|
yield return center + Vector2Int.down * i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async UniTask MoveGemsDown() {
|
private async UniTask MoveGemsDown() {
|
||||||
@@ -189,14 +322,14 @@ namespace Services {
|
|||||||
await UniTask.Delay(5);
|
await UniTask.Delay(5);
|
||||||
RefillBoard();
|
RefillBoard();
|
||||||
await UniTask.Delay(5);
|
await UniTask.Delay(5);
|
||||||
|
|
||||||
this.matchService.FindAllMatches();
|
this.matchService.FindAllMatches();
|
||||||
if (this.matchService.CurrentMatches.Count > 0)
|
if (this.matchService.CurrentMatches.Count > 0) {
|
||||||
{
|
|
||||||
await UniTask.Delay(5);
|
await UniTask.Delay(5);
|
||||||
DestroyMatches();
|
|
||||||
}
|
// In cascades, there is no "creating slot" bomb protection.
|
||||||
else
|
await DestroyMatchesAsync(new List<Vector2Int>());
|
||||||
{
|
} else {
|
||||||
await UniTask.Delay(5);
|
await UniTask.Delay(5);
|
||||||
this.currentState = GameState.Move;
|
this.currentState = GameState.Move;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Enums;
|
using Enums;
|
||||||
|
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 MarkBombArea(Vector2Int bombPosition, int blastSize);
|
void SetLastSwap(Vector2Int from, Vector2Int to);
|
||||||
|
void ClearPendingBombs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using System.Linq;
|
|||||||
using Enums;
|
using Enums;
|
||||||
using Models.Interfaces;
|
using Models.Interfaces;
|
||||||
using Services.Interfaces;
|
using Services.Interfaces;
|
||||||
|
using Structs;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Services {
|
namespace Services {
|
||||||
@@ -10,14 +11,33 @@ namespace Services {
|
|||||||
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;
|
||||||
|
|
||||||
|
public List<BombSpawnRequest> pendingBombSpawns = new List<BombSpawnRequest>();
|
||||||
|
public IReadOnlyList<BombSpawnRequest> PendingBombSpawns => this.pendingBombSpawns;
|
||||||
|
|
||||||
|
private Vector2Int lastSwapFrom;
|
||||||
|
private Vector2Int lastSwapTo;
|
||||||
|
|
||||||
private IGameBoard gameBoard;
|
private 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;
|
||||||
|
|
||||||
|
if (gemTypeToCheck == GemType.Bomb)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (positionToCheck.x > 1)
|
if (positionToCheck.x > 1)
|
||||||
{
|
{
|
||||||
if (gems[positionToCheck.x - 1, positionToCheck.y].Type == gemTypeToCheck && gems[positionToCheck.x - 2, positionToCheck.y].Type == gemTypeToCheck)
|
if (gems[positionToCheck.x - 1, positionToCheck.y].Type == gemTypeToCheck && gems[positionToCheck.x - 2, positionToCheck.y].Type == gemTypeToCheck)
|
||||||
@@ -35,26 +55,19 @@ 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++) {
|
||||||
{
|
|
||||||
Gem currentGem = this.gameBoard.GemsGrid[x, y];
|
Gem currentGem = this.gameBoard.GemsGrid[x, y];
|
||||||
if (currentGem != null)
|
if (currentGem == null)
|
||||||
{
|
continue;
|
||||||
if (x > 0 && x < this.gameBoard.Width - 1)
|
|
||||||
{
|
if (x > 0 && x < this.gameBoard.Width - 1) {
|
||||||
Gem leftGem = this.gameBoard.GemsGrid[x - 1, y];
|
Gem leftGem = this.gameBoard.GemsGrid[x - 1, y];
|
||||||
Gem rightGem = this.gameBoard.GemsGrid[x + 1, y];
|
Gem rightGem = this.gameBoard.GemsGrid[x + 1, y];
|
||||||
//checking no empty spots
|
if (leftGem != null && rightGem != null) {
|
||||||
if (leftGem != null && rightGem != null)
|
if (leftGem.MatchColor == currentGem.MatchColor && rightGem.MatchColor == currentGem.MatchColor) {
|
||||||
{
|
|
||||||
//Match
|
|
||||||
if (leftGem.Type == currentGem.Type && rightGem.Type == currentGem.Type)
|
|
||||||
{
|
|
||||||
currentGem.isMatch = true;
|
|
||||||
leftGem.isMatch = true;
|
|
||||||
rightGem.isMatch = true;
|
|
||||||
this.currentMatches.Add(currentGem);
|
this.currentMatches.Add(currentGem);
|
||||||
this.currentMatches.Add(leftGem);
|
this.currentMatches.Add(leftGem);
|
||||||
this.currentMatches.Add(rightGem);
|
this.currentMatches.Add(rightGem);
|
||||||
@@ -62,19 +75,11 @@ namespace Services {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (y > 0 && y < this.gameBoard.Height - 1)
|
if (y > 0 && y < this.gameBoard.Height - 1) {
|
||||||
{
|
|
||||||
Gem aboveGem = this.gameBoard.GemsGrid[x, y - 1];
|
Gem aboveGem = this.gameBoard.GemsGrid[x, y - 1];
|
||||||
Gem bellowGem = this.gameBoard.GemsGrid[x, y + 1];
|
Gem bellowGem = this.gameBoard.GemsGrid[x, y + 1];
|
||||||
//checking no empty spots
|
if (aboveGem != null && bellowGem != null) {
|
||||||
if (aboveGem != null && bellowGem != null)
|
if (aboveGem.MatchColor == currentGem.MatchColor && bellowGem.MatchColor == currentGem.MatchColor) {
|
||||||
{
|
|
||||||
//Match
|
|
||||||
if (aboveGem.Type == currentGem.Type && bellowGem.Type == currentGem.Type)
|
|
||||||
{
|
|
||||||
currentGem.isMatch = true;
|
|
||||||
aboveGem.isMatch = true;
|
|
||||||
bellowGem.isMatch = true;
|
|
||||||
this.currentMatches.Add(currentGem);
|
this.currentMatches.Add(currentGem);
|
||||||
this.currentMatches.Add(aboveGem);
|
this.currentMatches.Add(aboveGem);
|
||||||
this.currentMatches.Add(bellowGem);
|
this.currentMatches.Add(bellowGem);
|
||||||
@@ -82,11 +87,69 @@ namespace Services {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.currentMatches.Count > 0)
|
||||||
|
this.currentMatches = this.currentMatches.Distinct().ToList();
|
||||||
|
|
||||||
|
DetectBombSpawnFromLastSwap();
|
||||||
|
CheckForBombs();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.currentMatches.Count > 0) this.currentMatches = this.currentMatches.Distinct().ToList();
|
private void DetectBombSpawnFromLastSwap() {
|
||||||
|
Vector2Int from = this.lastSwapFrom;
|
||||||
|
Vector2Int to = this.lastSwapTo;
|
||||||
|
|
||||||
CheckForBombs();
|
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;
|
||||||
|
|
||||||
|
int horizontal = CountLine(pivot, Vector2Int.left) + 1 + CountLine(pivot, Vector2Int.right);
|
||||||
|
int vertical = CountLine(pivot, Vector2Int.up) + 1 + CountLine(pivot, Vector2Int.down);
|
||||||
|
|
||||||
|
int best = Mathf.Max(horizontal, vertical);
|
||||||
|
if (best < 4)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Spawn a bomb on the creating slot with the same color group.
|
||||||
|
// 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 CountLine(Vector2Int start, Vector2Int direction) {
|
||||||
|
Gem startGem = this.gameBoard.GetGemAt(start);
|
||||||
|
if (startGem == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
GemType color = startGem.MatchColor;
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
Vector2Int position = start + direction;
|
||||||
|
|
||||||
|
while (position.x >= 0 && position.x < this.gameBoard.Width && position.y >= 0 && position.y < this.gameBoard.Height) {
|
||||||
|
Gem g = this.gameBoard.GetGemAt(position);
|
||||||
|
if (g == null || g.MatchColor != color)
|
||||||
|
break;
|
||||||
|
|
||||||
|
count++;
|
||||||
|
position += direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckForBombs() {
|
private void CheckForBombs() {
|
||||||
@@ -118,32 +181,38 @@ namespace Services {
|
|||||||
|
|
||||||
Gem neighbor = gems[newX, newY];
|
Gem neighbor = gems[newX, newY];
|
||||||
if (neighbor?.Type == GemType.Bomb)
|
if (neighbor?.Type == GemType.Bomb)
|
||||||
MarkBombArea(new Vector2Int(newX, newY), 1);
|
MarkBombCross(new Vector2Int(newX, newY), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MarkBombArea(Vector2Int bombPosition, int blastSize) {
|
private void MarkBombCross(Vector2Int bombPosition, int radius) {
|
||||||
Gem[,] gems = this.gameBoard.GemsGrid;
|
Gem[,] gems = this.gameBoard.GemsGrid;
|
||||||
int width = this.gameBoard.Width;
|
int width = this.gameBoard.Width;
|
||||||
int height = this.gameBoard.Height;
|
int height = this.gameBoard.Height;
|
||||||
|
|
||||||
for (int x = bombPosition.x - blastSize; x <= bombPosition.x + blastSize; x++)
|
void Mark(Vector2Int p) {
|
||||||
{
|
if (p.x < 0 || p.x >= width || p.y < 0 || p.y >= height)
|
||||||
for (int y = bombPosition.y - blastSize; y <= bombPosition.y + blastSize; y++)
|
return;
|
||||||
{
|
Gem g = gems[p.x, p.y];
|
||||||
if (x >= 0 && x < width && y >= 0 && y < height)
|
if (g == null)
|
||||||
{
|
return;
|
||||||
if (gems[x, y] != null)
|
|
||||||
{
|
this.currentMatches.Add(g);
|
||||||
gems[x, y].isMatch = true;
|
|
||||||
this.currentMatches.Add(gems[x, y]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mark(bombPosition);
|
||||||
|
|
||||||
|
for (int i = 1; i <= radius; i++) {
|
||||||
|
Mark(bombPosition + Vector2Int.left * i);
|
||||||
|
Mark(bombPosition + Vector2Int.right * i);
|
||||||
|
Mark(bombPosition + Vector2Int.up * i);
|
||||||
|
Mark(bombPosition + Vector2Int.down * i);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentMatches = this.currentMatches.Distinct().ToList();
|
this.currentMatches = this.currentMatches.Distinct().ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
14
Assets/Scripts/Structs/BombSpawnRequest.cs
Normal file
14
Assets/Scripts/Structs/BombSpawnRequest.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using Enums;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Structs {
|
||||||
|
public readonly struct BombSpawnRequest {
|
||||||
|
public Vector2Int Position { get; }
|
||||||
|
public GemType Color { get; }
|
||||||
|
|
||||||
|
public BombSpawnRequest(Vector2Int position, GemType color) {
|
||||||
|
Position = position;
|
||||||
|
Color = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Scripts/Structs/BombSpawnRequest.cs.meta
Normal file
3
Assets/Scripts/Structs/BombSpawnRequest.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c2f038fd08be49a58b1db5f2b7adc9d9
|
||||||
|
timeCreated: 1765740408
|
||||||
@@ -28,7 +28,7 @@ namespace Views {
|
|||||||
this.isFalling = true;
|
this.isFalling = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async UniTaskVoid UpdatePosition(Vector2Int positionBasedOnIndex) {
|
public async UniTaskVoid UpdatePosition(Vector2Int positionBasedOnIndex, float gemSpeed) {
|
||||||
if (!this.isFalling) {
|
if (!this.isFalling) {
|
||||||
await FallDelay();
|
await FallDelay();
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,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(), 0.05f);
|
this.transform.position = Vector2.Lerp(this.transform.position, positionBasedOnIndex.ToVector2(), gemSpeed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ namespace Views {
|
|||||||
this.scoreText = GetComponentInChildren<TextMeshProUGUI>();
|
this.scoreText = GetComponentInChildren<TextMeshProUGUI>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateScore() {
|
public void UpdateScore(float scoreSpeed) {
|
||||||
this.displayScore = Mathf.Lerp(this.displayScore, this.actualScore, 5 * Time.deltaTime);
|
this.displayScore = Mathf.Lerp(this.displayScore, this.actualScore, scoreSpeed * Time.deltaTime);
|
||||||
this.scoreText.text = this.displayScore.ToString("0");
|
this.scoreText.text = this.displayScore.ToString("0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user