match_magic/lib/game/board.dart

715 lines
19 KiB
Dart

import 'dart:math';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'tile.dart';
import 'package:flame/sprite.dart';
import 'swap_notifier.dart';
class Board extends FlameGame {
final List<Sprite> sprites;
final SwapNotifier swapNotifier;
static const int rows = 8;
static const int cols = 8;
late double tileSize;
List<List<Tile?>> tiles = [];
int? selectedRow;
int? selectedCol;
bool animating = false;
Tile? lastMovedTile;
Tile? lastMovedByGravity;
bool isFirstLaunch = true;
Board({required this.sprites, required this.swapNotifier});
@override
Future<void> onLoad() async {
super.onLoad();
_resetGame();
}
void _resetGame() {
tiles.clear();
selectedRow = null;
selectedCol = null;
animating = false;
tileSize = size.x / cols;
_initializeGrid(isFirstLaunch);
_removeInitialMatches();
isFirstLaunch = false;
}
void restartGame() {
isFirstLaunch = true;
_resetGame();
swapNotifier.resetScore();
}
void _initializeGrid(bool animate) {
for (int row = 0; row < rows; row++) {
List<Tile?> rowTiles = [];
for (int col = 0; col < cols; col++) {
int spriteIndex = _randomElement();
var initialPosition = animate
? Vector2(col * tileSize, -tileSize * rows)
: Vector2(col * tileSize, row * tileSize);
var tile = Tile(
sprite: sprites[spriteIndex],
spriteIndex: spriteIndex,
size: Vector2.all(tileSize),
position: initialPosition,
row: row,
col: col,
onSwipe: handleTileSwipe,
);
rowTiles.add(tile);
add(tile);
if (animate) {
double delay = 0.04 * ((rows - 1 - row) * cols + col);
tile.add(
MoveEffect.to(
Vector2(col * tileSize, row * tileSize),
EffectController(
duration: 0.5,
startDelay: delay,
curve: Curves.bounceOut,
),
),
);
}
}
tiles.add(rowTiles);
}
}
int _randomElement() {
return Random().nextInt(sprites.length);
}
void _removeInitialMatches() {
bool hasMatches;
do {
hasMatches = false;
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
if (_hasMatch(row, col)) {
int spriteIndex = _randomElement();
tiles[row][col]!.sprite = sprites[spriteIndex];
tiles[row][col]!.spriteIndex = spriteIndex;
hasMatches = true;
}
}
}
} while (hasMatches);
}
Future<void> handleTileSwipe(Tile tile, Vector2 delta) async {
if (animating) return;
int row = tile.row;
int col = tile.col;
Tile? targetTile;
if (delta.x.abs() > delta.y.abs()) {
if (delta.x > 0 && col < cols - 1) {
targetTile = tiles[row][col + 1];
} else if (delta.x < 0 && col > 0) {
targetTile = tiles[row][col - 1];
}
} else {
if (delta.y > 0 && row < rows - 1) {
targetTile = tiles[row + 1][col];
} else if (delta.y < 0 && row > 0) {
targetTile = tiles[row - 1][col];
}
}
if (targetTile != null) {
animating = true;
lastMovedTile = tile;
swapTiles(tile, targetTile, true);
await Future.delayed(const Duration(milliseconds: 300));
if (!checkMatches()) {
swapTiles(tile, targetTile, true);
} else {
swapNotifier.incrementMoveCount();
}
selectedRow = null;
selectedCol = null;
animating = false;
}
}
bool _isAdjacent(int row1, int col1, int row2, int col2) {
return (row1 == row2 && (col1 - col2).abs() == 1) ||
(col1 == col2 && (row1 - row2).abs() == 1);
}
void swapTiles(Tile tile1, Tile tile2, bool animate) {
// final tempPosition1 = tile1.position.clone();
// final tempPosition2 = tile2.position.clone();
final tempRow1 = tile1.row;
final tempCol1 = tile1.col;
final tempRow2 = tile2.row;
final tempCol2 = tile2.col;
tile1.row = tempRow2;
tile1.col = tempCol2;
tile2.row = tempRow1;
tile2.col = tempCol1;
tiles[tile1.row][tile1.col] = tile1;
tiles[tile2.row][tile2.col] = tile2;
if (animate) {
final tempPosition1 = tile1.position.clone();
final tempPosition2 = tile2.position.clone();
tile1.animateMoveTo(tempPosition2, () {});
tile2.animateMoveTo(tempPosition1, () {});
}
tile1.deselect();
tile1.deselect();
}
bool checkMatches({bool simulate = false}) {
if (simulate) {
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
if (_hasMatch(row, col)) {
return true;
}
}
}
return false;
}
animating = true;
final matches = <List<int>>[];
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
if (_hasMatch(row, col)) {
matches.add([row, col]);
}
}
}
if (matches.isNotEmpty) {
int points = 0;
for (final match in matches) {
points += _removeMatchedElements(match[0], match[1]);
}
Future.delayed(const Duration(milliseconds: 300), () {
_applyGravity();
Future.delayed(const Duration(milliseconds: 300), () {
_fillEmptySpaces();
Future.delayed(const Duration(milliseconds: 300), () {
animating = false;
checkMatches();
});
});
});
swapNotifier.incrementScore(points);
return true;
}
animating = false;
return false;
}
bool _hasMatch(int row, int col) {
final value = tiles[row][col]?.spriteIndex;
int count = 1;
for (int i = col + 1; i < cols && tiles[row][i]?.spriteIndex == value; i++)
count++;
for (int i = col - 1; i >= 0 && tiles[row][i]?.spriteIndex == value; i--)
count++;
if (count >= 3) return true;
count = 1;
for (int i = row + 1; i < rows && tiles[i][col]?.spriteIndex == value; i++)
count++;
for (int i = row - 1; i >= 0 && tiles[i][col]?.spriteIndex == value; i--)
count++;
return count >= 3;
}
int _removeMatchedElements(int row, int col) {
int score = 0;
final int? value = tiles[row][col]?.spriteIndex;
bool bombTriggered = false;
Tile? tileToTransformIntoBomb = null;
int left = col;
while (left > 0 && tiles[row][left - 1]?.spriteIndex == value) left--;
int right = col;
while (right < cols - 1 && tiles[row][right + 1]?.spriteIndex == value)
right++;
if (right - left + 1 >= 3) {
score += _calculateScore(right - left + 1);
if (right - left + 1 >= 4) {
tileToTransformIntoBomb = lastMovedTile ?? lastMovedByGravity;
}
for (int i = left; i <= right; i++) {
if (tiles[row][i] != null) {
if (tiles[row][i]!.isBomb) {
bombTriggered = true;
_triggerBomb(row, i);
}
if (tiles[row][i] != tileToTransformIntoBomb) {
_animateRemoveTile(tiles[row][i]!);
tiles[row][i] = null;
}
}
}
}
int top = row;
while (top > 0 && tiles[top - 1][col]?.spriteIndex == value) top--;
int bottom = row;
while (bottom < rows - 1 && tiles[bottom + 1][col]?.spriteIndex == value)
bottom++;
if (bottom - top + 1 >= 3) {
score += _calculateScore(bottom - top + 1);
if (bottom - top + 1 >= 4) {
tileToTransformIntoBomb = lastMovedTile ?? lastMovedByGravity;
}
for (int i = top; i <= bottom; i++) {
if (tiles[i][col] != null) {
if (tiles[i][col]!.isBomb) {
bombTriggered = true;
_triggerBomb(i, col);
}
if (tiles[i][col] != tileToTransformIntoBomb) {
_animateRemoveTile(tiles[i][col]!);
tiles[i][col] = null;
}
}
}
}
if (bombTriggered) {
_triggerBomb(row, col);
}
if (tileToTransformIntoBomb != null) {
_createBomb(tileToTransformIntoBomb.row, tileToTransformIntoBomb.col);
}
return score;
}
void _triggerBomb(int row, int col) {
final tile = tiles[row][col];
if (tile == null || !tile.isBomb) return;
final bombPosition = tile.position.clone();
for (int i = max(0, row - 1); i <= min(rows - 1, row + 1); i++) {
for (int j = max(0, col - 1); j <= min(cols - 1, col + 1); j++) {
if (tiles[i][j] != null) {
if (tiles[i][j]!.isBomb && !(i == row && j == col)) {
tiles[i][j]!.isBomb = false;
_triggerBomb(i, j);
} else {
_animateRemoveTile(tiles[i][j]!);
tiles[i][j] = null;
}
}
}
}
_animateBombExplosion(bombPosition);
_animateRemoveTile(tile);
tiles[row][col] = null;
}
void _animateBombExplosion(Vector2 position) {
final explosion = CircleComponent(
radius: tileSize / 2,
paint: Paint()..color = Colors.orange.withOpacity(0.7),
position: position - Vector2.all(tileSize / 2),
);
add(explosion);
explosion.add(
ScaleEffect.to(
Vector2.all(3),
EffectController(duration: 0.5),
onComplete: () => explosion.removeFromParent(),
),
);
}
int _calculateScore(int matchLength) {
if (matchLength == 3) {
return 50;
} else if (matchLength == 4) {
return 100;
} else if (matchLength == 5) {
return 200;
}
return 0;
}
void _animateRemoveTile(Tile tile) {
tile.add(RemoveEffect(
delay: 0.5,
onComplete: () => remove(tile),
));
}
void _createBomb(int row, int col) {
final tile = tiles[row][col];
if (tile != null) {
tile.isBomb = true;
tile.add(
OpacityEffect.to(
0.5,
EffectController(
duration: 0.5,
infinite: true,
reverseDuration: 0.5,
),
),
);
tile.add(
ColorEffect(
Colors.orange,
EffectController(
duration: 0.5,
infinite: true,
reverseDuration: 0.5,
),
opacityFrom: 0.5,
opacityTo: 1.0,
),
);
}
}
void _createMagicCube(int row, int col) {
final tile = tiles[row][col]!;
tile.isMagicCube = true;
tile.add(
OpacityEffect.to(
0.5,
EffectController(
duration: 0.5,
infinite: true,
reverseDuration: 0.5,
),
),
);
tile.add(
ColorEffect(
Colors.purple,
EffectController(
duration: 0.5,
infinite: true,
reverseDuration: 0.5,
),
opacityFrom: 0.5,
opacityTo: 1.0,
),
);
}
void explodeBomb(Tile bombTile) {
final bombPosition = bombTile.position.clone();
final bombRow = bombTile.row;
final bombCol = bombTile.col;
for (int i = max(0, bombRow - 1); i <= min(rows - 1, bombRow + 1); i++) {
for (int j = max(0, bombCol - 1); j <= min(cols - 1, bombCol + 1); j++) {
if (tiles[i][j] != null) {
if (tiles[i][j]!.isBomb && (i != bombRow || j != bombCol)) {
_triggerBomb(i, j);
} else {
_animateRemoveTile(tiles[i][j]!);
tiles[i][j] = null;
}
}
}
}
_animateBombExplosion(bombPosition);
tiles[bombRow][bombCol] = null;
}
void _applyGravity() {
for (int col = 0; col < cols; col++) {
for (int row = rows - 1; row >= 0; row--) {
if (tiles[row][col] == null) {
for (int k = row - 1; k >= 0; k--) {
if (tiles[k][col] != null) {
tiles[row][col] = tiles[k][col]!;
tiles[k][col] = null;
tiles[row][col]!.row = row;
tiles[row][col]!.animateMoveTo(
Vector2(col * tileSize, row * tileSize), () {});
lastMovedByGravity = tiles[row][col];
break;
}
}
}
}
}
}
void _fillEmptySpaces() {
for (int col = 0; col < cols; col++) {
for (int row = rows - 1; row >= 0; row--) {
if (tiles[row][col] == null) {
int spriteIndex = _randomElement();
var tile = Tile(
sprite: sprites[spriteIndex],
spriteIndex: spriteIndex,
size: Vector2.all(tileSize),
position: Vector2(col * tileSize, -tileSize),
row: row,
col: col,
// onTileTap: handleTileTap,
onSwipe: handleTileSwipe,
);
tiles[row][col] = tile;
add(tile);
tile.animateMoveTo(Vector2(col * tileSize, row * tileSize), () {});
}
}
}
}
Tile? findHint() {
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
Tile? tile = tiles[row][col];
if (col < cols - 1 && _canSwap(row, col, row, col + 1)) {
return tile;
}
if (row < rows - 1 && _canSwap(row, col, row + 1, col)) {
return tile;
}
}
}
return null;
}
bool _canSwap(int row1, int col1, int row2, int col2) {
Tile tempTile1 = tiles[row1][col1]!;
Tile tempTile2 = tiles[row2][col2]!;
tiles[row1][col1] = tempTile2;
tiles[row2][col2] = tempTile1;
bool matchFound = checkMatches(simulate: true);
tiles[row1][col1] = tempTile1;
tiles[row2][col2] = tempTile2;
return matchFound;
}
void showHint() {
Tile? hintTile = findHint();
if (hintTile != null) {
hintTile.select();
}
}
}
// void handleTileTap(Tile tappedTile) {
// if (animating) return;
// int row = tappedTile.row;
// int col = tappedTile.col;
// if (selectedRow == null || selectedCol == null) {
// tappedTile.select();
// selectedRow = row;
// selectedCol = col;
// } else {
// tiles[selectedRow!][selectedCol!]?.deselect();
// if (_isAdjacent(selectedRow!, selectedCol!, row, col)) {
// lastMovedTile = tiles[selectedRow!][selectedCol!];
// swapTiles(tiles[selectedRow!][selectedCol!]!, tiles[row][col]!, true);
// Future.delayed(const Duration(milliseconds: 300), () {
// if (!checkMatches()) {
// swapTiles(
// tiles[row][col]!, tiles[selectedRow!][selectedCol!]!, true);
// }
// selectedRow = null;
// selectedCol = null;
// });
// } else {
// tiles[selectedRow!][selectedCol!]?.deselect();
// tappedTile.select();
// selectedRow = row;
// selectedCol = col;
// }
// }
// }
// int _removeMatchedElements(int row, int col) {
// int score = 0;
// final int? value = tiles[row][col]?.spriteIndex;
// bool bombTriggered = false;
// Tile? tileToTransformIntoBomb = null;
// int left = col;
// while (left > 0 && tiles[row][left - 1]?.spriteIndex == value) left--;
// int right = col;
// while (right < cols - 1 && tiles[row][right + 1]?.spriteIndex == value)
// right++;
// if (right - left + 1 >= 3) {
// score += _calculateScore(right - left + 1);
// if (right - left + 1 >= 4 &&
// lastMovedTile != null &&
// lastMovedTile!.row == row &&
// lastMovedTile!.col >= left &&
// lastMovedTile!.col <= right) {
// tileToTransformIntoBomb = lastMovedTile;
// }
// for (int i = left; i <= right; i++) {
// if (tiles[row][i] != null) {
// if (tiles[row][i]!.isBomb) {
// bombTriggered = true;
// _triggerBomb(row, i);
// }
// if (tiles[row][i] != tileToTransformIntoBomb) {
// _animateRemoveTile(tiles[row][i]!);
// tiles[row][i] = null;
// }
// }
// }
// }
// int top = row;
// while (top > 0 && tiles[top - 1][col]?.spriteIndex == value) top--;
// int bottom = row;
// while (bottom < rows - 1 && tiles[bottom + 1][col]?.spriteIndex == value)
// bottom++;
// if (bottom - top + 1 >= 3) {
// score += _calculateScore(bottom - top + 1);
// if (bottom - top + 1 >= 4 &&
// lastMovedTile != null &&
// lastMovedTile!.col == col &&
// lastMovedTile!.row >= top &&
// lastMovedTile!.row <= bottom) {
// tileToTransformIntoBomb = lastMovedTile;
// }
// for (int i = top; i <= bottom; i++) {
// if (tiles[i][col] != null) {
// if (tiles[i][col]!.isBomb) {
// bombTriggered = true;
// _triggerBomb(i, col);
// }
// if (tiles[i][col] != tileToTransformIntoBomb) {
// _animateRemoveTile(tiles[i][col]!);
// tiles[i][col] = null;
// }
// }
// }
// }
// if (bombTriggered) {
// _triggerBomb(row, col);
// }
// if (tileToTransformIntoBomb != null) {
// _createBomb(tileToTransformIntoBomb.row, tileToTransformIntoBomb.col);
// }
// return score;
// }
// void explodeBomb(Tile bombTile) {
// final bombPosition = bombTile.position;
// final bombRow = bombTile.row;
// final bombCol = bombTile.col;
// for (int rowOffset = -1; rowOffset <= 1; rowOffset++) {
// for (int colOffset = -1; colOffset <= 1; colOffset++) {
// final row = bombRow + rowOffset;
// final col = bombCol + colOffset;
// if (row >= 0 && row < rows && col >= 0 && col < cols) {
// final tile = tiles[row][col];
// if (tile != null && tile != bombTile) {
// _animateRemoveTile(tile);
// tiles[row][col] = null;
// }
// }
// }
// }
// bombTile.add(RemoveEffect(
// delay: 0.5,
// onComplete: () => remove(bombTile),
// ));
// final explosion = CircleComponent(
// radius: tileSize / 2,
// paint: Paint()..color = Colors.orange.withOpacity(0.7),
// position: bombPosition,
// );
// add(explosion);
// explosion.add(ScaleEffect.to(
// Vector2.all(2),
// EffectController(duration: 0.5),
// onComplete: () => explosion.removeFromParent(),
// ));
// }
// void _applyGravity() {
// for (int col = 0; col < cols; col++) {
// for (int row = rows - 1; row >= 0; row--) {
// if (tiles[row][col] == null) {
// for (int k = row - 1; k >= 0; k--) {
// if (tiles[k][col] != null) {
// tiles[row][col] = tiles[k][col]!;
// tiles[k][col] = null;
// tiles[row][col]!.row = row;
// tiles[row][col]!.animateMoveTo(
// Vector2(col * tileSize, row * tileSize), () {});
// break;
// }
// }
// }
// }
// }
// }