Change Bomb Implementation
- L and T shapes are no longer considered a match - Bomb explodes WITH gems - Bomb will have a small sprite of the creating gem
This commit is contained in:
@@ -21,21 +21,6 @@ namespace Services
|
|||||||
return matchPositions.Distinct().ToList();
|
return matchPositions.Distinct().ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the old implementation wherein any adjacent matches will detonate the bomb
|
|
||||||
private List<Vector2Int> DetonateThroughAdjacentMatches(IReadOnlyList<Vector2Int> matchPositions) {
|
|
||||||
HashSet<Vector2Int> candidates = new HashSet<Vector2Int>();
|
|
||||||
|
|
||||||
foreach (Vector2Int p in matchPositions)
|
|
||||||
{
|
|
||||||
candidates.Add(p + Vector2Int.left);
|
|
||||||
candidates.Add(p + Vector2Int.right);
|
|
||||||
candidates.Add(p + Vector2Int.up);
|
|
||||||
candidates.Add(p + Vector2Int.down);
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidates.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UniTask DetonateChainAsync(
|
public async UniTask DetonateChainAsync(
|
||||||
IReadOnlyList<Vector2Int> initialBombs,
|
IReadOnlyList<Vector2Int> initialBombs,
|
||||||
Func<Vector2Int, bool> inBounds,
|
Func<Vector2Int, bool> inBounds,
|
||||||
@@ -134,31 +119,16 @@ namespace Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<Vector2Int> DiamondRing(Vector2Int center, int distance)
|
|
||||||
{
|
|
||||||
for (int distanceX = -distance; distanceX <= distance; distanceX++)
|
|
||||||
{
|
|
||||||
int distanceY = distance - Mathf.Abs(distanceX);
|
|
||||||
if (distanceY == 0)
|
|
||||||
{
|
|
||||||
yield return new Vector2Int(center.x + distanceX, center.y);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
yield return new Vector2Int(center.x + distanceX, center.y + distanceY);
|
|
||||||
yield return new Vector2Int(center.x + distanceX, center.y - distanceY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<Vector2Int> DiamondAreaInclusive(Vector2Int center, int radius)
|
private static IEnumerable<Vector2Int> DiamondAreaInclusive(Vector2Int center, int radius)
|
||||||
{
|
{
|
||||||
yield return center;
|
// Manhattan-distance filled diamond:
|
||||||
|
for (int distanceX = -radius; distanceX <= radius; distanceX++)
|
||||||
for (int dist = 1; dist <= radius; dist++)
|
|
||||||
{
|
{
|
||||||
foreach (Vector2Int pivot in DiamondRing(center, dist))
|
int remaining = radius - Mathf.Abs(distanceX);
|
||||||
yield return pivot;
|
for (int distanceY = -remaining; distanceY <= remaining; distanceY++)
|
||||||
|
{
|
||||||
|
yield return new Vector2Int(center.x + distanceX, center.y + distanceY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -306,6 +306,14 @@ namespace Services {
|
|||||||
Gem currentGem = this.gameBoard.GetGemAt(new Vector2Int(x,y));
|
Gem currentGem = this.gameBoard.GetGemAt(new Vector2Int(x,y));
|
||||||
if (currentGem == null) {
|
if (currentGem == null) {
|
||||||
int gemToUse = RandomUtils.RandomGemTypeAsInt();
|
int gemToUse = RandomUtils.RandomGemTypeAsInt();
|
||||||
|
|
||||||
|
int iterations = 0;
|
||||||
|
while (this.matchService.MatchesAt(new Vector2Int(x, y), (GemType)gemToUse) && iterations < 100)
|
||||||
|
{
|
||||||
|
gemToUse = RandomUtils.RandomGemTypeAsInt();
|
||||||
|
iterations++;
|
||||||
|
}
|
||||||
|
|
||||||
SpawnGem(new Vector2Int(x, y), (GemType)gemToUse);
|
SpawnGem(new Vector2Int(x, y), (GemType)gemToUse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,19 +34,30 @@ namespace Services {
|
|||||||
|
|
||||||
public bool MatchesAt(Vector2Int positionToCheck, GemType gemTypeToCheck) {
|
public bool MatchesAt(Vector2Int positionToCheck, GemType gemTypeToCheck) {
|
||||||
Gem[,] gems = this.gameBoard.GemsGrid;
|
Gem[,] gems = this.gameBoard.GemsGrid;
|
||||||
|
|
||||||
|
// We don't prevent spawning bombs via this rule (and bombs shouldn't be treated as a normal color here).
|
||||||
if (gemTypeToCheck == GemType.Bomb)
|
if (gemTypeToCheck == GemType.Bomb)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (positionToCheck.x > 1)
|
// Check horizontal: would placing gemTypeToCheck at positionToCheck create XXX with 2-left?
|
||||||
{
|
if (positionToCheck.x > 1) {
|
||||||
if (gems[positionToCheck.x - 1, positionToCheck.y].Type == gemTypeToCheck && gems[positionToCheck.x - 2, positionToCheck.y].Type == gemTypeToCheck)
|
Gem left1 = gems[positionToCheck.x - 1, positionToCheck.y];
|
||||||
|
Gem left2 = gems[positionToCheck.x - 2, positionToCheck.y];
|
||||||
|
|
||||||
|
if (left1 != null && left2 != null &&
|
||||||
|
left1.MatchColor == gemTypeToCheck &&
|
||||||
|
left2.MatchColor == gemTypeToCheck)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (positionToCheck.y > 1)
|
// Check vertical: would placing gemTypeToCheck at positionToCheck create XXX with 2-down?
|
||||||
{
|
if (positionToCheck.y > 1) {
|
||||||
if (gems[positionToCheck.x, positionToCheck.y - 1].Type == gemTypeToCheck && gems[positionToCheck.x, positionToCheck.y - 2].Type == gemTypeToCheck)
|
Gem down1 = gems[positionToCheck.x, positionToCheck.y - 1];
|
||||||
|
Gem down2 = gems[positionToCheck.x, positionToCheck.y - 2];
|
||||||
|
|
||||||
|
if (down1 != null && down2 != null &&
|
||||||
|
down1.MatchColor == gemTypeToCheck &&
|
||||||
|
down2.MatchColor == gemTypeToCheck)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,80 +99,12 @@ namespace Services {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.currentMatches.Count > 0)
|
|
||||||
this.currentMatches = this.currentMatches.Distinct().ToList();
|
|
||||||
|
|
||||||
ExpandMatchesForLTShapes();
|
|
||||||
|
|
||||||
if (this.currentMatches.Count > 0)
|
if (this.currentMatches.Count > 0)
|
||||||
this.currentMatches = this.currentMatches.Distinct().ToList();
|
this.currentMatches = this.currentMatches.Distinct().ToList();
|
||||||
|
|
||||||
DetectBombSpawnFromLastSwap();
|
DetectBombSpawnFromLastSwap();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExpandMatchesForLTShapes() {
|
|
||||||
if (this.currentMatches.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
List<Gem> seeds = this.currentMatches.ToList();
|
|
||||||
|
|
||||||
foreach (Gem pivotGem in seeds) {
|
|
||||||
if (pivotGem == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Vector2Int pivot = pivotGem.Position;
|
|
||||||
|
|
||||||
int left = CountLine(pivot, Vector2Int.left);
|
|
||||||
int right = CountLine(pivot, Vector2Int.right);
|
|
||||||
int up = CountLine(pivot, Vector2Int.up);
|
|
||||||
int down = CountLine(pivot, Vector2Int.down);
|
|
||||||
|
|
||||||
int horizontalLen = left + 1 + right;
|
|
||||||
int verticalLen = up + 1 + down;
|
|
||||||
|
|
||||||
bool hasHorizontalMatch = horizontalLen >= 3;
|
|
||||||
bool hasVerticalMatch = verticalLen >= 3;
|
|
||||||
|
|
||||||
// one more in the perpendicular direction = length = 2
|
|
||||||
bool hasHorizontalArm = horizontalLen >= 2;
|
|
||||||
bool hasVerticalArm = verticalLen >= 2;
|
|
||||||
|
|
||||||
// Main horizontal + vertical arm = include vertical run
|
|
||||||
if (hasHorizontalMatch && hasVerticalArm) {
|
|
||||||
AddRun(pivot, Vector2Int.up);
|
|
||||||
AddRun(pivot, Vector2Int.down);
|
|
||||||
this.currentMatches.Add(pivotGem);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main vertical + horizontal arm = include horizontal run
|
|
||||||
if (hasVerticalMatch && hasHorizontalArm) {
|
|
||||||
AddRun(pivot, Vector2Int.left);
|
|
||||||
AddRun(pivot, Vector2Int.right);
|
|
||||||
this.currentMatches.Add(pivotGem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddRun(Vector2Int start, Vector2Int direction) {
|
|
||||||
Gem startGem = this.gameBoard.GetGemAt(start);
|
|
||||||
if (startGem == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
GemType color = startGem.MatchColor;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
this.currentMatches.Add(g);
|
|
||||||
position += direction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DetectBombSpawnFromLastSwap() {
|
private void DetectBombSpawnFromLastSwap() {
|
||||||
Vector2Int from = this.lastSwapFrom;
|
Vector2Int from = this.lastSwapFrom;
|
||||||
Vector2Int to = this.lastSwapTo;
|
Vector2Int to = this.lastSwapTo;
|
||||||
@@ -182,9 +125,8 @@ namespace Services {
|
|||||||
if (this.currentMatches.All(g => g.Position != pivot))
|
if (this.currentMatches.All(g => g.Position != pivot))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// NEW RULE:
|
// If the matched group that includes this pivot has 4+ connected gems, spawn a bomb.
|
||||||
// If the match group that includes this pivot (including L/T expansions) has 4+ gems, spawn a bomb.
|
int groupSize = GetMatchedGroupSize(pivot);
|
||||||
int groupSize = GetMatchedGroupSizeIncludingLT(pivot);
|
|
||||||
if (groupSize < 4)
|
if (groupSize < 4)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -195,14 +137,13 @@ namespace Services {
|
|||||||
this.pendingBombSpawns.Add(new BombSpawnRequest(pivot, pivotGem.MatchColor));
|
this.pendingBombSpawns.Add(new BombSpawnRequest(pivot, pivotGem.MatchColor));
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetMatchedGroupSizeIncludingLT(Vector2Int pivot) {
|
private int GetMatchedGroupSize(Vector2Int pivot) {
|
||||||
Gem pivotGem = this.gameBoard.GetGemAt(pivot);
|
Gem pivotGem = this.gameBoard.GetGemAt(pivot);
|
||||||
if (pivotGem == null)
|
if (pivotGem == null)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
GemType color = pivotGem.MatchColor;
|
GemType color = pivotGem.MatchColor;
|
||||||
|
|
||||||
// Only count gems that are actually in currentMatches (which already includes L/T expansions).
|
|
||||||
HashSet<Vector2Int> matchedPositions = new HashSet<Vector2Int>(
|
HashSet<Vector2Int> matchedPositions = new HashSet<Vector2Int>(
|
||||||
this.currentMatches
|
this.currentMatches
|
||||||
.Where(g => g != null && g.MatchColor == color)
|
.Where(g => g != null && g.MatchColor == color)
|
||||||
@@ -243,90 +184,5 @@ namespace Services {
|
|||||||
|
|
||||||
return visited.Count;
|
return visited.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user