Files
match3-unity/Assets/Scripts/Services/MatchService.cs
2025-12-15 03:44:53 +08:00

218 lines
7.8 KiB
C#

using System.Collections.Generic;
using System.Linq;
using Enums;
using Models.Interfaces;
using Services.Interfaces;
using Structs;
using UnityEngine;
namespace Services {
public class MatchService : IMatchService {
private List<Gem> currentMatches = new List<Gem>();
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;
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;
if (gemTypeToCheck == GemType.Bomb)
return false;
if (positionToCheck.x > 1)
{
if (gems[positionToCheck.x - 1, positionToCheck.y].Type == gemTypeToCheck && gems[positionToCheck.x - 2, positionToCheck.y].Type == gemTypeToCheck)
return true;
}
if (positionToCheck.y > 1)
{
if (gems[positionToCheck.x, positionToCheck.y - 1].Type == gemTypeToCheck && gems[positionToCheck.x, positionToCheck.y - 2].Type == gemTypeToCheck)
return true;
}
return false;
}
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++) {
Gem currentGem = this.gameBoard.GemsGrid[x, y];
if (currentGem == null)
continue;
if (x > 0 && x < this.gameBoard.Width - 1) {
Gem leftGem = this.gameBoard.GemsGrid[x - 1, y];
Gem rightGem = this.gameBoard.GemsGrid[x + 1, y];
if (leftGem != null && rightGem != null) {
if (leftGem.MatchColor == currentGem.MatchColor && rightGem.MatchColor == currentGem.MatchColor) {
this.currentMatches.Add(currentGem);
this.currentMatches.Add(leftGem);
this.currentMatches.Add(rightGem);
}
}
}
if (y > 0 && y < this.gameBoard.Height - 1) {
Gem aboveGem = this.gameBoard.GemsGrid[x, y - 1];
Gem bellowGem = this.gameBoard.GemsGrid[x, y + 1];
if (aboveGem != null && bellowGem != null) {
if (aboveGem.MatchColor == currentGem.MatchColor && bellowGem.MatchColor == currentGem.MatchColor) {
this.currentMatches.Add(currentGem);
this.currentMatches.Add(aboveGem);
this.currentMatches.Add(bellowGem);
}
}
}
}
if (this.currentMatches.Count > 0)
this.currentMatches = this.currentMatches.Distinct().ToList();
DetectBombSpawnFromLastSwap();
CheckForBombs();
}
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;
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() {
Gem[,] gems = this.gameBoard.GemsGrid;
int width = this.gameBoard.Width;
int height = this.gameBoard.Height;
for (int i = 0; i < this.currentMatches.Count; i++)
{
Gem gem = this.currentMatches[i];
int x = gem.Position.x;
int y = gem.Position.y;
Vector2Int[] directions =
{
Vector2Int.left,
Vector2Int.right,
Vector2Int.down,
Vector2Int.up
};
foreach (Vector2Int direction in directions)
{
int newX = x + direction.x;
int newY = y + direction.y;
if (newX < 0 || newX >= width || newY < 0 || newY >= height)
continue;
Gem neighbor = gems[newX, newY];
if (neighbor?.Type == GemType.Bomb)
MarkBombCross(new Vector2Int(newX, newY), 1);
}
}
}
private void MarkBombCross(Vector2Int bombPosition, int radius) {
Gem[,] gems = this.gameBoard.GemsGrid;
int width = this.gameBoard.Width;
int height = this.gameBoard.Height;
void Mark(Vector2Int p) {
if (p.x < 0 || p.x >= width || p.y < 0 || p.y >= height)
return;
Gem g = gems[p.x, p.y];
if (g == null)
return;
this.currentMatches.Add(g);
}
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();
}
}
}