2024-08-05 08:04:54 +00:00
|
|
|
import 'dart:math';
|
|
|
|
import 'package:flame/components.dart';
|
2024-08-06 18:00:51 +00:00
|
|
|
import 'package:flame/effects.dart';
|
2024-08-05 08:04:54 +00:00
|
|
|
import 'package:flame/game.dart';
|
|
|
|
import 'tile.dart';
|
|
|
|
import 'package:flame/sprite.dart';
|
2024-08-13 14:50:11 +00:00
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'swap_notifier.dart';
|
2024-08-05 08:04:54 +00:00
|
|
|
|
|
|
|
class Board extends FlameGame {
|
|
|
|
final List<Sprite> sprites;
|
2024-08-13 14:50:11 +00:00
|
|
|
final SwapNotifier swapNotifier;
|
2024-08-05 08:04:54 +00:00
|
|
|
static const int rows = 8;
|
|
|
|
static const int cols = 8;
|
|
|
|
late double tileSize;
|
|
|
|
List<List<Tile?>> tiles = [];
|
|
|
|
int? selectedRow;
|
|
|
|
int? selectedCol;
|
2024-08-06 18:00:51 +00:00
|
|
|
bool animating = false;
|
2024-08-05 08:04:54 +00:00
|
|
|
|
2024-08-13 14:50:11 +00:00
|
|
|
Board({required this.sprites, required this.swapNotifier});
|
2024-08-05 08:04:54 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> onLoad() async {
|
|
|
|
super.onLoad();
|
2024-08-13 14:50:11 +00:00
|
|
|
_resetGame();
|
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
|
2024-08-13 14:50:11 +00:00
|
|
|
void _resetGame() {
|
|
|
|
tiles.clear();
|
|
|
|
selectedRow = null;
|
|
|
|
selectedCol = null;
|
|
|
|
animating = false;
|
2024-08-05 08:04:54 +00:00
|
|
|
tileSize = size.x / cols;
|
|
|
|
_initializeGrid();
|
|
|
|
_removeInitialMatches();
|
|
|
|
}
|
|
|
|
|
2024-08-13 14:50:11 +00:00
|
|
|
void restartGame() {
|
|
|
|
_resetGame();
|
|
|
|
swapNotifier.resetScore();
|
|
|
|
}
|
|
|
|
|
2024-08-05 08:04:54 +00:00
|
|
|
void _initializeGrid() {
|
|
|
|
for (int row = 0; row < rows; row++) {
|
|
|
|
List<Tile?> rowTiles = [];
|
|
|
|
for (int col = 0; col < cols; col++) {
|
|
|
|
int spriteIndex = _randomElement();
|
|
|
|
var tile = Tile(
|
|
|
|
sprite: sprites[spriteIndex],
|
|
|
|
spriteIndex: spriteIndex,
|
|
|
|
size: Vector2.all(tileSize),
|
|
|
|
position: Vector2(col * tileSize, row * tileSize),
|
|
|
|
row: row,
|
|
|
|
col: col,
|
|
|
|
onTileTap: handleTileTap,
|
2024-08-08 20:50:55 +00:00
|
|
|
onSwipe: handleTileSwipe,
|
2024-08-05 08:04:54 +00:00
|
|
|
);
|
|
|
|
rowTiles.add(tile);
|
|
|
|
add(tile);
|
|
|
|
}
|
|
|
|
tiles.add(rowTiles);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int _randomElement() {
|
|
|
|
return Random().nextInt(7);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
void handleTileTap(Tile tappedTile) {
|
2024-08-06 18:00:51 +00:00
|
|
|
if (animating) return;
|
|
|
|
|
2024-08-05 08:04:54 +00:00
|
|
|
int row = tappedTile.row;
|
|
|
|
int col = tappedTile.col;
|
|
|
|
|
|
|
|
if (selectedRow == null || selectedCol == null) {
|
2024-08-06 18:00:51 +00:00
|
|
|
tappedTile.select();
|
2024-08-05 08:04:54 +00:00
|
|
|
selectedRow = row;
|
|
|
|
selectedCol = col;
|
|
|
|
} else {
|
2024-08-06 18:00:51 +00:00
|
|
|
tiles[selectedRow!][selectedCol!]?.deselect();
|
2024-08-05 08:04:54 +00:00
|
|
|
if (_isAdjacent(selectedRow!, selectedCol!, row, col)) {
|
2024-08-06 18:00:51 +00:00
|
|
|
swapTiles(tiles[selectedRow!]![selectedCol!]!, tiles[row]![col]!, true);
|
2024-08-05 08:04:54 +00:00
|
|
|
Future.delayed(const Duration(milliseconds: 300), () {
|
2024-08-08 20:50:55 +00:00
|
|
|
if (!checkMatches()) {
|
2024-08-06 18:00:51 +00:00
|
|
|
swapTiles(
|
|
|
|
tiles[row]![col]!, tiles[selectedRow!]![selectedCol!]!, true);
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
selectedRow = null;
|
|
|
|
selectedCol = null;
|
|
|
|
});
|
|
|
|
} else {
|
2024-08-06 18:00:51 +00:00
|
|
|
tiles[selectedRow!][selectedCol!]?.deselect();
|
|
|
|
tappedTile.select();
|
2024-08-05 08:04:54 +00:00
|
|
|
selectedRow = row;
|
|
|
|
selectedCol = col;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-08 20:50:55 +00:00
|
|
|
Future<void> handleTileSwipe(Tile tile, Vector2 delta) async {
|
2024-08-13 14:50:11 +00:00
|
|
|
if (animating) return;
|
2024-08-08 20:50:55 +00:00
|
|
|
|
|
|
|
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) {
|
2024-08-13 14:50:11 +00:00
|
|
|
animating = true;
|
2024-08-08 20:50:55 +00:00
|
|
|
swapTiles(tile, targetTile, true);
|
|
|
|
|
|
|
|
await Future.delayed(const Duration(milliseconds: 300));
|
|
|
|
|
|
|
|
if (!checkMatches()) {
|
|
|
|
swapTiles(tile, targetTile!, true);
|
|
|
|
}
|
|
|
|
|
2024-08-13 14:50:11 +00:00
|
|
|
animating = false;
|
2024-08-08 20:50:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-05 08:04:54 +00:00
|
|
|
bool _isAdjacent(int row1, int col1, int row2, int col2) {
|
|
|
|
return (row1 == row2 && (col1 - col2).abs() == 1) ||
|
|
|
|
(col1 == col2 && (row1 - row2).abs() == 1);
|
|
|
|
}
|
|
|
|
|
2024-08-06 18:00:51 +00:00
|
|
|
void swapTiles(Tile tile1, Tile tile2, bool animate) {
|
2024-08-08 20:50:55 +00:00
|
|
|
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;
|
2024-08-05 08:04:54 +00:00
|
|
|
|
2024-08-08 20:50:55 +00:00
|
|
|
tile1.row = tempRow2;
|
|
|
|
tile1.col = tempCol2;
|
|
|
|
tile2.row = tempRow1;
|
|
|
|
tile2.col = tempCol1;
|
2024-08-05 08:04:54 +00:00
|
|
|
|
|
|
|
tiles[tile1.row][tile1.col] = tile1;
|
|
|
|
tiles[tile2.row][tile2.col] = tile2;
|
|
|
|
|
2024-08-06 18:00:51 +00:00
|
|
|
if (animate) {
|
2024-08-08 20:50:55 +00:00
|
|
|
tile1.animateMoveTo(tempPosition2, () {});
|
|
|
|
tile2.animateMoveTo(tempPosition1, () {});
|
2024-08-06 18:00:51 +00:00
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
|
2024-08-08 20:50:55 +00:00
|
|
|
bool checkMatches() {
|
2024-08-13 14:50:11 +00:00
|
|
|
animating = true;
|
2024-08-06 18:00:51 +00:00
|
|
|
|
2024-08-05 08:04:54 +00:00
|
|
|
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) {
|
2024-08-13 14:50:11 +00:00
|
|
|
int points = 0;
|
2024-08-05 08:04:54 +00:00
|
|
|
for (final match in matches) {
|
2024-08-13 14:50:11 +00:00
|
|
|
points += _removeMatchedElements(match[0], match[1]);
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
Future.delayed(const Duration(milliseconds: 300), () {
|
2024-08-06 18:00:51 +00:00
|
|
|
_applyGravity();
|
|
|
|
Future.delayed(const Duration(milliseconds: 300), () {
|
|
|
|
_fillEmptySpaces();
|
|
|
|
Future.delayed(const Duration(milliseconds: 300), () {
|
2024-08-13 14:50:11 +00:00
|
|
|
animating = false;
|
2024-08-08 20:50:55 +00:00
|
|
|
checkMatches();
|
2024-08-06 18:00:51 +00:00
|
|
|
});
|
|
|
|
});
|
2024-08-05 08:04:54 +00:00
|
|
|
});
|
2024-08-13 14:50:11 +00:00
|
|
|
swapNotifier.incrementScore(points);
|
|
|
|
|
2024-08-05 08:04:54 +00:00
|
|
|
return true;
|
|
|
|
}
|
2024-08-13 14:50:11 +00:00
|
|
|
animating = false;
|
2024-08-05 08:04:54 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool _hasMatch(int row, int col) {
|
2024-08-06 18:00:51 +00:00
|
|
|
final value = tiles[row]?[col]?.spriteIndex;
|
2024-08-05 08:04:54 +00:00
|
|
|
|
|
|
|
int count = 1;
|
2024-08-06 18:00:51 +00:00
|
|
|
for (int i = col + 1; i < cols && tiles[row]?[i]?.spriteIndex == value; i++)
|
2024-08-05 08:04:54 +00:00
|
|
|
count++;
|
2024-08-06 18:00:51 +00:00
|
|
|
for (int i = col - 1; i >= 0 && tiles[row]?[i]?.spriteIndex == value; i--)
|
2024-08-05 08:04:54 +00:00
|
|
|
count++;
|
|
|
|
if (count >= 3) return true;
|
|
|
|
|
|
|
|
count = 1;
|
2024-08-06 18:00:51 +00:00
|
|
|
for (int i = row + 1; i < rows && tiles[i]?[col]?.spriteIndex == value; i++)
|
2024-08-05 08:04:54 +00:00
|
|
|
count++;
|
2024-08-06 18:00:51 +00:00
|
|
|
for (int i = row - 1; i >= 0 && tiles[i]?[col]?.spriteIndex == value; i--)
|
2024-08-05 08:04:54 +00:00
|
|
|
count++;
|
|
|
|
return count >= 3;
|
|
|
|
}
|
|
|
|
|
2024-08-13 14:50:11 +00:00
|
|
|
int _removeMatchedElements(int row, int col) {
|
|
|
|
int score = 0;
|
2024-08-06 18:00:51 +00:00
|
|
|
final value = tiles[row]?[col]?.spriteIndex;
|
2024-08-05 08:04:54 +00:00
|
|
|
|
|
|
|
int left = col;
|
2024-08-06 18:00:51 +00:00
|
|
|
while (left > 0 && tiles[row]?[left - 1]?.spriteIndex == value) left--;
|
2024-08-05 08:04:54 +00:00
|
|
|
int right = col;
|
2024-08-06 18:00:51 +00:00
|
|
|
while (right < cols - 1 && tiles[row]?[right + 1]?.spriteIndex == value)
|
2024-08-05 08:04:54 +00:00
|
|
|
right++;
|
|
|
|
if (right - left + 1 >= 3) {
|
2024-08-13 14:50:11 +00:00
|
|
|
int matchLength = right - left + 1;
|
|
|
|
score += _calculateScore(matchLength);
|
2024-08-05 08:04:54 +00:00
|
|
|
for (int i = left; i <= right; i++) {
|
2024-08-06 18:00:51 +00:00
|
|
|
if (tiles[row]?[i] != null) {
|
|
|
|
_animateRemoveTile(tiles[row]![i]!);
|
|
|
|
tiles[row]![i] = null;
|
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int top = row;
|
2024-08-06 18:00:51 +00:00
|
|
|
while (top > 0 && tiles[top - 1]?[col]?.spriteIndex == value) top--;
|
2024-08-05 08:04:54 +00:00
|
|
|
int bottom = row;
|
2024-08-06 18:00:51 +00:00
|
|
|
while (bottom < rows - 1 && tiles[bottom + 1]?[col]?.spriteIndex == value)
|
2024-08-05 08:04:54 +00:00
|
|
|
bottom++;
|
|
|
|
if (bottom - top + 1 >= 3) {
|
2024-08-13 14:50:11 +00:00
|
|
|
int matchLength = bottom - top + 1;
|
|
|
|
score += _calculateScore(matchLength);
|
2024-08-05 08:04:54 +00:00
|
|
|
for (int i = top; i <= bottom; i++) {
|
2024-08-06 18:00:51 +00:00
|
|
|
if (tiles[i]?[col] != null) {
|
|
|
|
_animateRemoveTile(tiles[i]![col]!);
|
|
|
|
tiles[i]![col] = null;
|
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
}
|
2024-08-13 14:50:11 +00:00
|
|
|
|
|
|
|
return score;
|
|
|
|
}
|
|
|
|
|
|
|
|
int _calculateScore(int matchLength) {
|
|
|
|
if (matchLength == 3) {
|
|
|
|
return 50;
|
|
|
|
} else if (matchLength == 4) {
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
return 0;
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
|
2024-08-06 18:00:51 +00:00
|
|
|
void _animateRemoveTile(Tile tile) {
|
|
|
|
tile.animateRemove(() {
|
|
|
|
remove(tile);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-08-05 08:04:54 +00:00
|
|
|
void _applyGravity() {
|
|
|
|
for (int col = 0; col < cols; col++) {
|
|
|
|
for (int row = rows - 1; row >= 0; row--) {
|
2024-08-06 18:00:51 +00:00
|
|
|
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;
|
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _fillEmptySpaces() {
|
|
|
|
for (int col = 0; col < cols; col++) {
|
|
|
|
for (int row = rows - 1; row >= 0; row--) {
|
2024-08-06 18:00:51 +00:00
|
|
|
if (tiles[row]?[col] == null) {
|
2024-08-05 08:04:54 +00:00
|
|
|
int spriteIndex = _randomElement();
|
|
|
|
var tile = Tile(
|
|
|
|
sprite: sprites[spriteIndex],
|
|
|
|
spriteIndex: spriteIndex,
|
|
|
|
size: Vector2.all(tileSize),
|
2024-08-06 18:00:51 +00:00
|
|
|
position: Vector2(col * tileSize, -tileSize),
|
2024-08-05 08:04:54 +00:00
|
|
|
row: row,
|
|
|
|
col: col,
|
|
|
|
onTileTap: handleTileTap,
|
2024-08-13 14:50:11 +00:00
|
|
|
onSwipe: handleTileSwipe,
|
2024-08-05 08:04:54 +00:00
|
|
|
);
|
|
|
|
tiles[row][col] = tile;
|
|
|
|
add(tile);
|
2024-08-06 18:00:51 +00:00
|
|
|
tile.animateMoveTo(Vector2(col * tileSize, row * tileSize), () {});
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|