This commit is contained in:
2025-12-17 00:55:30 +08:00
parent 85b9767201
commit 3786863b00
13 changed files with 37 additions and 73 deletions

View File

@@ -3,10 +3,10 @@ using UnityEngine;
namespace Services { namespace Services {
public class GameBoard : IGameBoard { public class GameBoard : IGameBoard {
private int height, width; private readonly int height, width;
public int Height => this.height; public int Height => this.height;
public int Width => this.width; public int Width => this.width;
private Gem[,] gemsGrid; private readonly Gem[,] gemsGrid;
public Gem[,] GemsGrid => this.gemsGrid; public Gem[,] GemsGrid => this.gemsGrid;
public GameBoard(int width, int height) { public GameBoard(int width, int height) {
@@ -17,7 +17,7 @@ namespace Services {
public Gem GetGemAt(Vector2Int pos) { public Gem GetGemAt(Vector2Int pos) {
Gem gameObject = this.gemsGrid[pos.x, pos.y]; Gem gameObject = this.gemsGrid[pos.x, pos.y];
return gameObject != null ? gameObject : null; return gameObject;
} }
public void SetGemAt(Vector2Int pos, Gem gameObject) { public void SetGemAt(Vector2Int pos, Gem gameObject) {

View File

@@ -4,15 +4,15 @@ using UnityEngine;
namespace Services { namespace Services {
public class Gem { public class Gem {
private GemType type; private readonly 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 readonly int scoreValue;
public int ScoreValue => this.scoreValue; public int ScoreValue => this.scoreValue;
private GemType colorType; private readonly GemType colorType;
public GemType MatchColor => this.type == GemType.Bomb ? this.colorType : this.type; public GemType MatchColor => this.type == GemType.Bomb ? this.colorType : this.type;
public Gem(GemType type, Vector2Int position, GemTypeValues gemValue, GemType? colorType = null) { public Gem(GemType type, Vector2Int position, GemTypeValues gemValue, GemType? colorType = null) {

View File

@@ -1,13 +1,11 @@
using Services; using Services;
using UnityEngine;
using Utils; using Utils;
using VContainer.Unity;
using Views; using Views;
namespace Presenter { namespace Presenter {
public class GemPresenter { public class GemPresenter {
private Gem gem; private readonly Gem gem;
private GemView gemView; private readonly GemView gemView;
public Gem Gem => this.gem; public Gem Gem => this.gem;
public GemView GemView => this.gemView; public GemView GemView => this.gemView;

View File

@@ -1,12 +1,11 @@
using System; using System;
using Services.Interfaces; using Services.Interfaces;
using VContainer.Unity;
using Views; using Views;
namespace Presenter { namespace Presenter {
public class ScorePresenter : IDisposable{ public class ScorePresenter : IDisposable{
private IScoreService scoreService; private readonly IScoreService scoreService;
private ScoreView scoreView; private readonly ScoreView scoreView;
public ScorePresenter(IScoreService scoreService, ScoreView scoreView) { public ScorePresenter(IScoreService scoreService, ScoreView scoreView) {
this.scoreService = scoreService; this.scoreService = scoreService;
this.scoreView = scoreView; this.scoreView = scoreView;

View File

@@ -1,4 +1,3 @@
using Models;
using Models.Interfaces; using Models.Interfaces;
using Presenter; using Presenter;
using ScriptableObjects; using ScriptableObjects;
@@ -23,14 +22,14 @@ namespace Scopes
builder.RegisterComponentInHierarchy<ScoreView>(); builder.RegisterComponentInHierarchy<ScoreView>();
builder.Register<IGameBoard>(c => builder.Register<IGameBoard>(_ =>
new GameBoard(this.gameVariables.width, this.gameVariables.height), new GameBoard(this.gameVariables.width, this.gameVariables.height),
Lifetime.Scoped); Lifetime.Scoped);
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<IObjectPool<GemView>>(c => builder.Register<IObjectPool<GemView>>(_ =>
new ObjectPoolService(this.gameVariables.gemsPrefabs, this.gemsHolder), new ObjectPoolService(this.gameVariables.gemsPrefabs, this.gemsHolder),
Lifetime.Scoped); Lifetime.Scoped);

View File

@@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using Enums;
using Structs; using Structs;
using UnityEngine; using UnityEngine;
using Views;
namespace ScriptableObjects { namespace ScriptableObjects {
[CreateAssetMenu(fileName = "GameVariables", menuName = "Game Variables")] [CreateAssetMenu(fileName = "GameVariables", menuName = "Game Variables")]

View File

@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using Enums; using Enums;
using Models;
using Services.Interfaces; using Services.Interfaces;
using UnityEngine; using UnityEngine;

View File

@@ -13,7 +13,6 @@ using Utils;
using VContainer.Unity; using VContainer.Unity;
using Views; using Views;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
using Random = UnityEngine.Random;
namespace Services { namespace Services {
public class GameBoardService : IGameBoardService, ITickable, IDisposable { public class GameBoardService : IGameBoardService, ITickable, IDisposable {

View File

@@ -42,7 +42,7 @@ namespace Services {
private void TryEmitSwap(Vector2 downScreen, Vector2 upScreen) private void TryEmitSwap(Vector2 downScreen, Vector2 upScreen)
{ {
if (this.inputCamera == null) return; if (this.inputCamera is null) return;
Vector2 downWorld = this.inputCamera.ScreenToWorldPoint(downScreen); Vector2 downWorld = this.inputCamera.ScreenToWorldPoint(downScreen);
Vector2 upWorld = this.inputCamera.ScreenToWorldPoint(upScreen); Vector2 upWorld = this.inputCamera.ScreenToWorldPoint(upScreen);

View File

@@ -1,7 +1,5 @@
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using Enums;
using UnityEngine; using UnityEngine;
using Views;
namespace Services.Interfaces { namespace Services.Interfaces {
public interface IGameBoardService { public interface IGameBoardService {

View File

@@ -11,13 +11,13 @@ 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>(); private readonly List<BombSpawnRequest> pendingBombSpawns = new List<BombSpawnRequest>();
public IReadOnlyList<BombSpawnRequest> PendingBombSpawns => this.pendingBombSpawns; public IReadOnlyList<BombSpawnRequest> PendingBombSpawns => this.pendingBombSpawns;
private Vector2Int lastSwapFrom; private Vector2Int lastSwapFrom;
private Vector2Int lastSwapTo; private Vector2Int lastSwapTo;
private IGameBoard gameBoard; private readonly IGameBoard gameBoard;
public MatchService(IGameBoard gameBoard) { public MatchService(IGameBoard gameBoard) {
this.gameBoard = gameBoard; this.gameBoard = gameBoard;
@@ -125,9 +125,9 @@ namespace Services {
if (this.currentMatches.All(g => g.Position != pivot)) if (this.currentMatches.All(g => g.Position != pivot))
return; return;
// If the matched group that includes this pivot has 4+ connected gems, spawn a bomb. // Only create a bomb if pivot is part of a straight 4+ line of the SAME color.
int groupSize = GetMatchedGroupSize(pivot); int longestLine = GetLongestMatchedLineThroughPivot(pivot, pivotGem.MatchColor);
if (groupSize < 4) if (longestLine < 4)
return; return;
// Prevent duplicates for the same cell. // Prevent duplicates for the same cell.
@@ -137,52 +137,30 @@ namespace Services {
this.pendingBombSpawns.Add(new BombSpawnRequest(pivot, pivotGem.MatchColor)); this.pendingBombSpawns.Add(new BombSpawnRequest(pivot, pivotGem.MatchColor));
} }
private int GetMatchedGroupSize(Vector2Int pivot) { private int GetLongestMatchedLineThroughPivot(Vector2Int pivot, GemType color) {
Gem pivotGem = this.gameBoard.GetGemAt(pivot); int horizontal = 1 + CountSameColorInDirection(pivot, Vector2Int.left, color)
if (pivotGem == null) + CountSameColorInDirection(pivot, Vector2Int.right, color);
return 0;
GemType color = pivotGem.MatchColor; int vertical = 1 + CountSameColorInDirection(pivot, Vector2Int.up, color)
+ CountSameColorInDirection(pivot, Vector2Int.down, color);
HashSet<Vector2Int> matchedPositions = new HashSet<Vector2Int>( return Mathf.Max(horizontal, vertical);
this.currentMatches }
.Where(g => g != null && g.MatchColor == color)
.Select(g => g.Position)
);
if (!matchedPositions.Contains(pivot)) private int CountSameColorInDirection(Vector2Int start, Vector2Int direction, GemType color) {
return 0; int count = 0;
Vector2Int oivot = start + direction;
Queue<Vector2Int> queue = new Queue<Vector2Int>(); while (oivot.x >= 0 && oivot.x < this.gameBoard.Width && oivot.y >= 0 && oivot.y < this.gameBoard.Height) {
HashSet<Vector2Int> visited = new HashSet<Vector2Int>(); Gem g = this.gameBoard.GetGemAt(oivot);
if (g == null || g.Type == GemType.Bomb || g.MatchColor != color)
break;
queue.Enqueue(pivot); count++;
visited.Add(pivot); oivot += direction;
Vector2Int[] directions = {
Vector2Int.left,
Vector2Int.right,
Vector2Int.up,
Vector2Int.down
};
while (queue.Count > 0) {
Vector2Int currentPivot = queue.Dequeue();
for (int i = 0; i < directions.Length; i++) {
Vector2Int n = currentPivot + directions[i];
if (visited.Contains(n))
continue;
if (!matchedPositions.Contains(n))
continue;
visited.Add(n);
queue.Enqueue(n);
}
} }
return visited.Count; return count;
} }
} }
} }

View File

@@ -1,6 +1,5 @@
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using Enums;
using Services; using Services;
using Structs; using Structs;
using UnityEngine; using UnityEngine;

View File

@@ -1,12 +1,11 @@
using System;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
namespace Views { namespace Views {
public class ScoreView : MonoBehaviour { public class ScoreView : MonoBehaviour {
private TextMeshProUGUI scoreText; private TextMeshProUGUI scoreText;
private float displayScore = 0; private float displayScore;
private int actualScore = 0; private int actualScore;
private void Awake() { private void Awake() {
this.scoreText = GetComponentInChildren<TextMeshProUGUI>(); this.scoreText = GetComponentInChildren<TextMeshProUGUI>();