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';
|
2024-08-16 16:09:24 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2024-09-27 13:40:14 +00:00
|
|
|
import 'package:match_magic/db/main_db.dart';
|
|
|
|
import 'package:match_magic/game/game_mode_manager.dart';
|
|
|
|
import 'package:match_magic/game/sprite_loader.dart';
|
|
|
|
import 'package:match_magic/screens/game_over_screen.dart';
|
2024-09-09 12:09:43 +00:00
|
|
|
import 'package:match_magic/utilities/audio_manager.dart';
|
2024-09-27 13:40:14 +00:00
|
|
|
import 'package:match_magic/widgets/overlays/game_overlay/hint_button.dart';
|
|
|
|
import 'package:match_magic/widgets/overlays/game_overlay/pause_button.dart';
|
|
|
|
import 'package:match_magic/widgets/overlays/game_overlay/restart_button.dart';
|
2024-08-05 08:04:54 +00:00
|
|
|
import 'tile.dart';
|
|
|
|
|
|
|
|
class Board extends FlameGame {
|
2024-09-27 13:40:14 +00:00
|
|
|
BuildContext context;
|
|
|
|
final bool gameMode;
|
|
|
|
late GameModeManager gameModeManager;
|
|
|
|
|
2024-08-05 08:04:54 +00:00
|
|
|
static const int rows = 8;
|
|
|
|
static const int cols = 8;
|
|
|
|
late double tileSize;
|
2024-09-27 13:40:14 +00:00
|
|
|
Tile? selectedTile;
|
2024-08-05 08:04:54 +00:00
|
|
|
List<List<Tile?>> tiles = [];
|
|
|
|
int? selectedRow;
|
|
|
|
int? selectedCol;
|
2024-08-06 18:00:51 +00:00
|
|
|
bool animating = false;
|
2024-09-27 13:40:14 +00:00
|
|
|
bool isGridInitialized = false;
|
2024-08-16 16:09:24 +00:00
|
|
|
Tile? lastMovedTile;
|
2024-08-29 13:44:44 +00:00
|
|
|
Tile? lastMovedByGravity;
|
2024-08-05 08:04:54 +00:00
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
static int score = 0;
|
|
|
|
late TextComponent _playerScore;
|
|
|
|
late TextComponent _playerMoves;
|
|
|
|
late TextComponent _remainingTime;
|
2024-09-04 12:11:40 +00:00
|
|
|
bool isFirstLaunch = true;
|
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
bool isGameOver = false;
|
|
|
|
static bool isSoundPlaying = true;
|
|
|
|
|
|
|
|
Board(
|
|
|
|
this.context, {
|
|
|
|
required this.gameMode,
|
|
|
|
}) {
|
|
|
|
gameModeManager = GameModeManager(
|
|
|
|
currentMode: gameMode,
|
|
|
|
onGameOver: _onGameOver,
|
|
|
|
);
|
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> onLoad() async {
|
|
|
|
super.onLoad();
|
2024-09-27 13:40:14 +00:00
|
|
|
await loadImages();
|
|
|
|
gameModeManager.initializeMode();
|
|
|
|
|
|
|
|
newGame();
|
2024-08-13 14:50:11 +00:00
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
void newGame() {
|
|
|
|
isFirstLaunch = true;
|
|
|
|
resetGame();
|
|
|
|
gameModeManager.initializeMode();
|
2024-08-13 14:50:11 +00:00
|
|
|
tiles.clear();
|
|
|
|
selectedRow = null;
|
|
|
|
selectedCol = null;
|
|
|
|
animating = false;
|
2024-08-05 08:04:54 +00:00
|
|
|
tileSize = size.x / cols;
|
2024-09-04 12:11:40 +00:00
|
|
|
_initializeGrid(isFirstLaunch);
|
2024-08-05 08:04:54 +00:00
|
|
|
_removeInitialMatches();
|
2024-09-27 13:40:14 +00:00
|
|
|
|
2024-09-04 12:11:40 +00:00
|
|
|
isFirstLaunch = false;
|
2024-09-27 13:40:14 +00:00
|
|
|
_playerScore = TextComponent(
|
|
|
|
text: 'Score: ',
|
|
|
|
position: Vector2(60, 70),
|
|
|
|
anchor: Anchor.centerLeft,
|
|
|
|
);
|
|
|
|
add(_playerScore);
|
|
|
|
if (gameModeManager.currentMode) {
|
|
|
|
_playerMoves = TextComponent(
|
|
|
|
text: 'Moves: ',
|
|
|
|
position: Vector2(200, 70),
|
|
|
|
anchor: Anchor.centerLeft,
|
|
|
|
);
|
|
|
|
add(_playerMoves);
|
|
|
|
} else {
|
|
|
|
_remainingTime = TextComponent(
|
|
|
|
text: 'Time: ',
|
|
|
|
position: Vector2(200, 70),
|
|
|
|
anchor: Anchor.centerLeft,
|
|
|
|
);
|
|
|
|
add(_remainingTime);
|
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
void resetGame() {
|
|
|
|
isGameOver = false;
|
|
|
|
this.overlays.remove(GameOverMenu.id);
|
|
|
|
|
|
|
|
score = 0;
|
|
|
|
gameModeManager.resetMode();
|
|
|
|
selectedTile = null;
|
|
|
|
final List<Component> tilesToRemove = [];
|
|
|
|
for (final component in children) {
|
|
|
|
if (component is Tile) {
|
|
|
|
tilesToRemove.add(component);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (final tile in tilesToRemove) {
|
|
|
|
remove(tile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void render(Canvas canvas) {
|
|
|
|
super.render(canvas);
|
|
|
|
|
|
|
|
// Progress bar renderer in level mode
|
|
|
|
if (gameModeManager.currentMode == GameMode.levelProgression) {
|
|
|
|
final progressBarWidth = size.x * (score / gameModeManager.targetScore);
|
|
|
|
final progressBarHeight = 20.0;
|
|
|
|
final rect = Rect.fromLTWH(10, size.y - progressBarHeight - 10,
|
|
|
|
progressBarWidth, progressBarHeight);
|
|
|
|
final paint = Paint()..color = Colors.green;
|
|
|
|
canvas.drawRect(rect, paint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void update(double dt) {
|
|
|
|
_playerScore.text = 'Score: $score';
|
|
|
|
if (gameModeManager.currentMode) {
|
|
|
|
_playerMoves.text = 'Moves: ${GameModeManager.movesLeft}';
|
|
|
|
} else {
|
|
|
|
_remainingTime.text = 'Time: ${gameModeManager.remainingTime}';
|
|
|
|
}
|
|
|
|
if (!isGameOver && gameModeManager.isGameOverCondition()) {
|
|
|
|
_onGameOver();
|
|
|
|
}
|
|
|
|
|
|
|
|
super.update(dt);
|
2024-08-13 14:50:11 +00:00
|
|
|
}
|
|
|
|
|
2024-09-04 12:11:40 +00:00
|
|
|
void _initializeGrid(bool animate) {
|
2024-09-27 13:40:14 +00:00
|
|
|
int totalTiles = rows * cols;
|
|
|
|
int completedTiles = 0;
|
2024-08-05 08:04:54 +00:00
|
|
|
for (int row = 0; row < rows; row++) {
|
|
|
|
List<Tile?> rowTiles = [];
|
|
|
|
for (int col = 0; col < cols; col++) {
|
|
|
|
int spriteIndex = _randomElement();
|
2024-09-04 12:11:40 +00:00
|
|
|
|
|
|
|
var initialPosition = animate
|
|
|
|
? Vector2(col * tileSize, -tileSize * rows)
|
|
|
|
: Vector2(col * tileSize, row * tileSize);
|
|
|
|
|
2024-08-05 08:04:54 +00:00
|
|
|
var tile = Tile(
|
2024-09-27 13:40:14 +00:00
|
|
|
sprite: Tile.crystals[spriteIndex],
|
2024-08-05 08:04:54 +00:00
|
|
|
spriteIndex: spriteIndex,
|
|
|
|
size: Vector2.all(tileSize),
|
2024-09-04 12:11:40 +00:00
|
|
|
position: initialPosition,
|
2024-08-05 08:04:54 +00:00
|
|
|
row: row,
|
|
|
|
col: col,
|
2024-08-08 20:50:55 +00:00
|
|
|
onSwipe: handleTileSwipe,
|
2024-08-05 08:04:54 +00:00
|
|
|
);
|
|
|
|
rowTiles.add(tile);
|
|
|
|
add(tile);
|
2024-09-04 12:11:40 +00:00
|
|
|
|
|
|
|
if (animate) {
|
|
|
|
double delay = 0.04 * ((rows - 1 - row) * cols + col);
|
|
|
|
|
|
|
|
tile.add(
|
|
|
|
MoveEffect.to(
|
2024-09-27 13:40:14 +00:00
|
|
|
Vector2(col * tileSize, row * tileSize + 200),
|
2024-09-04 12:11:40 +00:00
|
|
|
EffectController(
|
|
|
|
duration: 0.5,
|
|
|
|
startDelay: delay,
|
|
|
|
curve: Curves.bounceOut,
|
|
|
|
),
|
2024-09-27 13:40:14 +00:00
|
|
|
onComplete: () {
|
|
|
|
completedTiles++;
|
|
|
|
if (completedTiles == totalTiles) {
|
|
|
|
isGridInitialized = true;
|
|
|
|
}
|
|
|
|
},
|
2024-09-04 12:11:40 +00:00
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
tiles.add(rowTiles);
|
|
|
|
}
|
2024-09-27 13:40:14 +00:00
|
|
|
if (!animate) {
|
|
|
|
isGridInitialized = true;
|
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int _randomElement() {
|
2024-09-27 13:40:14 +00:00
|
|
|
return Random().nextInt(Tile.crystals.length);
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2024-09-27 13:40:14 +00:00
|
|
|
tiles[row][col]!.sprite = Tile.crystals[spriteIndex];
|
2024-08-05 08:04:54 +00:00
|
|
|
tiles[row][col]!.spriteIndex = spriteIndex;
|
|
|
|
hasMatches = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (hasMatches);
|
|
|
|
}
|
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
int _calculateScore(int matchLength) {
|
|
|
|
if (matchLength == 3) {
|
|
|
|
return 50;
|
|
|
|
} else if (matchLength == 4) {
|
|
|
|
return 400;
|
|
|
|
} else if (matchLength == 5) {
|
|
|
|
return 200;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Moving elements
|
|
|
|
|
|
|
|
void selectTile(Tile tile) {
|
|
|
|
if (selectedTile == null) {
|
|
|
|
selectedTile = tile;
|
|
|
|
tile.select();
|
|
|
|
} else {
|
|
|
|
if (_isNeighbor(selectedTile!, tile) || selectedTile!.isMagicCube) {
|
|
|
|
} else {
|
|
|
|
selectedTile?.deselect();
|
|
|
|
selectedTile = tile;
|
|
|
|
tile.select();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool _isNeighbor(Tile tile1, Tile tile2) {
|
|
|
|
return (tile1.row == tile2.row && (tile1.col - tile2.col).abs() == 1) ||
|
|
|
|
(tile1.col == tile2.col && (tile1.row - tile2.row).abs() == 1);
|
|
|
|
}
|
2024-09-09 12:09:43 +00:00
|
|
|
|
2024-08-08 20:50:55 +00:00
|
|
|
Future<void> handleTileSwipe(Tile tile, Vector2 delta) async {
|
2024-09-27 13:40:14 +00:00
|
|
|
if (!isGridInitialized || animating) return;
|
2024-08-08 20:50:55 +00:00
|
|
|
Tile? targetTile;
|
|
|
|
|
2024-09-09 12:09:43 +00:00
|
|
|
if (tile.isMagicCube) {
|
|
|
|
targetTile = _getTileBySwipeDirection(tile, delta);
|
|
|
|
if (targetTile != null) {
|
|
|
|
_removeAllOfType(targetTile.spriteIndex);
|
2024-09-27 13:40:14 +00:00
|
|
|
_animateRemoveTile(tile);
|
|
|
|
isSoundPlaying = await MainDB.instance.getSoundEnabled();
|
|
|
|
if (isSoundPlaying) {
|
|
|
|
AudioManager.playExplosionSound();
|
|
|
|
}
|
|
|
|
|
|
|
|
tiles[tile.row][tile.col] = null;
|
2024-08-08 20:50:55 +00:00
|
|
|
}
|
2024-09-09 12:09:43 +00:00
|
|
|
return;
|
2024-08-08 20:50:55 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 12:09:43 +00:00
|
|
|
targetTile = _getTileBySwipeDirection(tile, delta);
|
|
|
|
|
2024-08-08 20:50:55 +00:00
|
|
|
if (targetTile != null) {
|
2024-08-13 14:50:11 +00:00
|
|
|
animating = true;
|
2024-08-16 16:09:24 +00:00
|
|
|
lastMovedTile = tile;
|
2024-08-08 20:50:55 +00:00
|
|
|
swapTiles(tile, targetTile, true);
|
|
|
|
|
|
|
|
await Future.delayed(const Duration(milliseconds: 300));
|
2024-09-27 13:40:14 +00:00
|
|
|
bool matchesFound = await checkMatches();
|
|
|
|
if (!matchesFound) {
|
2024-08-16 16:09:24 +00:00
|
|
|
swapTiles(tile, targetTile, true);
|
2024-09-04 12:11:40 +00:00
|
|
|
} else {
|
2024-09-27 13:40:14 +00:00
|
|
|
GameModeManager.movesLeft--;
|
2024-08-08 20:50:55 +00:00
|
|
|
}
|
2024-08-29 13:44:44 +00:00
|
|
|
selectedRow = null;
|
|
|
|
selectedCol = null;
|
2024-08-13 14:50:11 +00:00
|
|
|
animating = false;
|
2024-08-08 20:50:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 12:09:43 +00:00
|
|
|
Tile? _getTileBySwipeDirection(Tile tile, Vector2 delta) {
|
|
|
|
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];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return targetTile;
|
|
|
|
}
|
|
|
|
|
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 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-29 13:44:44 +00:00
|
|
|
final tempPosition1 = tile1.position.clone();
|
|
|
|
final tempPosition2 = tile2.position.clone();
|
2024-08-08 20:50:55 +00:00
|
|
|
tile1.animateMoveTo(tempPosition2, () {});
|
|
|
|
tile2.animateMoveTo(tempPosition1, () {});
|
2024-08-06 18:00:51 +00:00
|
|
|
}
|
2024-08-29 13:44:44 +00:00
|
|
|
tile1.deselect();
|
|
|
|
tile1.deselect();
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
// Hint button
|
|
|
|
|
|
|
|
Future<Tile?> findHint() async {
|
|
|
|
for (int row = 0; row < rows; row++) {
|
|
|
|
for (int col = 0; col < cols; col++) {
|
|
|
|
Tile? tile = tiles[row][col];
|
|
|
|
|
|
|
|
if (col < cols - 1 && await _canSwap(row, col, row, col + 1)) {
|
|
|
|
return tile;
|
|
|
|
}
|
|
|
|
if (row < rows - 1 && await _canSwap(row, col, row + 1, col)) {
|
|
|
|
return tile;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool> _canSwap(int row1, int col1, int row2, int col2) async {
|
|
|
|
Tile tempTile1 = tiles[row1][col1]!;
|
|
|
|
Tile tempTile2 = tiles[row2][col2]!;
|
|
|
|
|
|
|
|
tiles[row1][col1] = tempTile2;
|
|
|
|
tiles[row2][col2] = tempTile1;
|
|
|
|
|
|
|
|
bool matchFound = await checkMatches(simulate: true);
|
|
|
|
|
|
|
|
tiles[row1][col1] = tempTile1;
|
|
|
|
tiles[row2][col2] = tempTile2;
|
|
|
|
|
|
|
|
return matchFound;
|
|
|
|
}
|
|
|
|
|
|
|
|
void showHint() async {
|
|
|
|
Tile? hintTile = await findHint();
|
|
|
|
if (hintTile != null) {
|
|
|
|
hintTile.select();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Match checks
|
|
|
|
|
|
|
|
Future<bool> checkMatches({bool simulate = false}) async {
|
2024-09-04 12:11:40 +00:00
|
|
|
if (simulate) {
|
|
|
|
for (int row = 0; row < rows; row++) {
|
|
|
|
for (int col = 0; col < cols; col++) {
|
|
|
|
if (_hasMatch(row, col)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
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
|
|
|
}
|
2024-09-27 13:40:14 +00:00
|
|
|
isSoundPlaying = await MainDB.instance.getSoundEnabled();
|
|
|
|
if (isSoundPlaying) {
|
|
|
|
AudioManager.playSelectSound();
|
|
|
|
}
|
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-09-27 13:40:14 +00:00
|
|
|
score += points;
|
2024-08-13 14:50:11 +00:00
|
|
|
|
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-16 16:09:24 +00:00
|
|
|
final value = tiles[row][col]?.spriteIndex;
|
2024-08-05 08:04:54 +00:00
|
|
|
|
|
|
|
int count = 1;
|
2024-09-27 13:40:14 +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-09-27 13:40:14 +00:00
|
|
|
}
|
|
|
|
for (int i = col - 1; i >= 0 && tiles[row][i]?.spriteIndex == value; i--) {
|
2024-08-05 08:04:54 +00:00
|
|
|
count++;
|
2024-09-27 13:40:14 +00:00
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
if (count >= 3) return true;
|
|
|
|
|
|
|
|
count = 1;
|
2024-09-27 13:40:14 +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-09-27 13:40:14 +00:00
|
|
|
}
|
|
|
|
for (int i = row - 1; i >= 0 && tiles[i][col]?.spriteIndex == value; i--) {
|
2024-08-05 08:04:54 +00:00
|
|
|
count++;
|
2024-09-27 13:40:14 +00:00
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
return count >= 3;
|
|
|
|
}
|
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
// Removing items
|
|
|
|
|
|
|
|
void _removeAllOfType(int spriteIndex) {
|
|
|
|
int removedCount = 0;
|
|
|
|
for (int row = 0; row < rows; row++) {
|
|
|
|
for (int col = 0; col < cols; col++) {
|
|
|
|
if (tiles[row][col]?.spriteIndex == spriteIndex) {
|
|
|
|
_animateRemoveTile(tiles[row][col]!);
|
|
|
|
tiles[row][col] = null;
|
|
|
|
removedCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
score += removedCount * 100;
|
|
|
|
Future.delayed(const Duration(milliseconds: 300), () {
|
|
|
|
_applyGravity();
|
|
|
|
Future.delayed(const Duration(milliseconds: 300), () {
|
|
|
|
_fillEmptySpaces();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-08-13 14:50:11 +00:00
|
|
|
int _removeMatchedElements(int row, int col) {
|
|
|
|
int score = 0;
|
2024-08-16 16:09:24 +00:00
|
|
|
final int? value = tiles[row][col]?.spriteIndex;
|
2024-09-27 13:40:14 +00:00
|
|
|
bool specialTriggered = false;
|
|
|
|
Tile? tileToTransformIntoSpecial;
|
2024-08-05 08:04:54 +00:00
|
|
|
|
|
|
|
int left = col;
|
2024-09-27 13:40:14 +00:00
|
|
|
while (left > 0 && tiles[row][left - 1]?.spriteIndex == value) {
|
|
|
|
left--;
|
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
int right = col;
|
2024-09-27 13:40:14 +00:00
|
|
|
while (right < cols - 1 && tiles[row][right + 1]?.spriteIndex == value) {
|
2024-08-05 08:04:54 +00:00
|
|
|
right++;
|
2024-09-27 13:40:14 +00:00
|
|
|
}
|
2024-08-16 16:09:24 +00:00
|
|
|
|
2024-08-05 08:04:54 +00:00
|
|
|
if (right - left + 1 >= 3) {
|
2024-08-21 17:45:01 +00:00
|
|
|
score += _calculateScore(right - left + 1);
|
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
if (right - left + 1 == 4) {
|
|
|
|
tileToTransformIntoSpecial = lastMovedTile ?? lastMovedByGravity;
|
|
|
|
} else if (right - left + 1 >= 5) {
|
|
|
|
tileToTransformIntoSpecial = lastMovedTile ?? lastMovedByGravity;
|
2024-08-16 16:09:24 +00:00
|
|
|
}
|
|
|
|
|
2024-08-05 08:04:54 +00:00
|
|
|
for (int i = left; i <= right; i++) {
|
2024-08-21 17:45:01 +00:00
|
|
|
if (tiles[row][i] != null) {
|
|
|
|
if (tiles[row][i]!.isBomb) {
|
2024-09-27 13:40:14 +00:00
|
|
|
specialTriggered = true;
|
2024-08-21 17:45:01 +00:00
|
|
|
_triggerBomb(row, i);
|
|
|
|
}
|
2024-09-27 13:40:14 +00:00
|
|
|
if (tiles[row][i] != tileToTransformIntoSpecial) {
|
2024-08-21 17:45:01 +00:00
|
|
|
_animateRemoveTile(tiles[row][i]!);
|
|
|
|
tiles[row][i] = null;
|
|
|
|
}
|
2024-08-06 18:00:51 +00:00
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int top = row;
|
2024-09-27 13:40:14 +00:00
|
|
|
while (top > 0 && tiles[top - 1][col]?.spriteIndex == value) {
|
|
|
|
top--;
|
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
int bottom = row;
|
2024-09-27 13:40:14 +00:00
|
|
|
while (bottom < rows - 1 && tiles[bottom + 1][col]?.spriteIndex == value) {
|
2024-08-05 08:04:54 +00:00
|
|
|
bottom++;
|
2024-09-27 13:40:14 +00:00
|
|
|
}
|
2024-08-16 16:09:24 +00:00
|
|
|
|
2024-08-05 08:04:54 +00:00
|
|
|
if (bottom - top + 1 >= 3) {
|
2024-08-21 17:45:01 +00:00
|
|
|
score += _calculateScore(bottom - top + 1);
|
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
if (bottom - top + 1 == 4) {
|
|
|
|
tileToTransformIntoSpecial = lastMovedTile ?? lastMovedByGravity;
|
|
|
|
} else if (bottom - top + 1 >= 5) {
|
|
|
|
tileToTransformIntoSpecial = lastMovedTile ?? lastMovedByGravity;
|
2024-08-16 16:09:24 +00:00
|
|
|
}
|
|
|
|
|
2024-08-05 08:04:54 +00:00
|
|
|
for (int i = top; i <= bottom; i++) {
|
2024-08-21 17:45:01 +00:00
|
|
|
if (tiles[i][col] != null) {
|
|
|
|
if (tiles[i][col]!.isBomb) {
|
2024-09-27 13:40:14 +00:00
|
|
|
specialTriggered = true;
|
2024-08-21 17:45:01 +00:00
|
|
|
_triggerBomb(i, col);
|
|
|
|
}
|
2024-09-27 13:40:14 +00:00
|
|
|
if (tiles[i][col] != tileToTransformIntoSpecial) {
|
2024-08-21 17:45:01 +00:00
|
|
|
_animateRemoveTile(tiles[i][col]!);
|
|
|
|
tiles[i][col] = null;
|
|
|
|
}
|
2024-08-06 18:00:51 +00:00
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
|
|
|
}
|
2024-08-13 14:50:11 +00:00
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
if (tileToTransformIntoSpecial != null) {
|
|
|
|
if ((right - left + 1 >= 5) || (bottom - top + 1 >= 5)) {
|
|
|
|
_createMagicCube(
|
|
|
|
tileToTransformIntoSpecial.row, tileToTransformIntoSpecial.col);
|
|
|
|
} else {
|
|
|
|
_createBomb(
|
|
|
|
tileToTransformIntoSpecial.row, tileToTransformIntoSpecial.col);
|
|
|
|
}
|
2024-08-21 17:45:01 +00:00
|
|
|
}
|
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
if (specialTriggered) {
|
|
|
|
_triggerBomb(row, col);
|
2024-08-16 16:09:24 +00:00
|
|
|
}
|
|
|
|
|
2024-08-13 14:50:11 +00:00
|
|
|
return score;
|
|
|
|
}
|
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
void _animateRemoveTile(Tile tile) {
|
|
|
|
tile.add(ScaleEffect.to(
|
|
|
|
Vector2.zero(),
|
|
|
|
EffectController(
|
|
|
|
duration: 0.2,
|
|
|
|
curve: Curves.easeInBack,
|
|
|
|
),
|
|
|
|
onComplete: () => tile.removeFromParent(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
// The emergence of new elements
|
|
|
|
|
|
|
|
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 + 200), () {});
|
|
|
|
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: Tile.crystals[spriteIndex],
|
|
|
|
spriteIndex: spriteIndex,
|
|
|
|
size: Vector2.all(tileSize),
|
|
|
|
position: Vector2(col * tileSize, -tileSize),
|
|
|
|
row: row,
|
|
|
|
col: col,
|
|
|
|
onSwipe: handleTileSwipe,
|
|
|
|
);
|
|
|
|
tiles[row][col] = tile;
|
|
|
|
add(tile);
|
|
|
|
tile.animateMoveTo(
|
|
|
|
Vector2(col * tileSize, row * tileSize + 200), () {});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bomb
|
|
|
|
|
|
|
|
void _triggerBomb(int row, int col) async {
|
2024-08-21 17:45:01 +00:00
|
|
|
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;
|
2024-09-27 13:40:14 +00:00
|
|
|
isSoundPlaying = await MainDB.instance.getSoundEnabled();
|
|
|
|
if (isSoundPlaying) {
|
|
|
|
AudioManager.playExplosionSound();
|
|
|
|
}
|
2024-08-21 17:45:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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(),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
void _createBomb(int row, int col) async {
|
|
|
|
isSoundPlaying = await MainDB.instance.getSoundEnabled();
|
|
|
|
if (isSoundPlaying) {
|
|
|
|
AudioManager.playFourElementsSound();
|
2024-08-13 14:50:11 +00:00
|
|
|
}
|
2024-08-21 17:45:01 +00:00
|
|
|
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,
|
|
|
|
),
|
2024-08-16 16:09:24 +00:00
|
|
|
),
|
2024-08-21 17:45:01 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
tile.add(
|
|
|
|
ColorEffect(
|
|
|
|
Colors.orange,
|
|
|
|
EffectController(
|
|
|
|
duration: 0.5,
|
|
|
|
infinite: true,
|
|
|
|
reverseDuration: 0.5,
|
|
|
|
),
|
|
|
|
opacityFrom: 0.5,
|
|
|
|
opacityTo: 1.0,
|
2024-08-16 16:09:24 +00:00
|
|
|
),
|
2024-08-21 17:45:01 +00:00
|
|
|
);
|
|
|
|
}
|
2024-08-16 16:09:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void explodeBomb(Tile bombTile) {
|
2024-08-29 13:44:44 +00:00
|
|
|
final bombPosition = bombTile.position.clone();
|
2024-08-16 16:09:24 +00:00
|
|
|
final bombRow = bombTile.row;
|
|
|
|
final bombCol = bombTile.col;
|
|
|
|
|
2024-08-29 13:44:44 +00:00
|
|
|
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;
|
2024-08-16 16:09:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-29 13:44:44 +00:00
|
|
|
_animateBombExplosion(bombPosition);
|
|
|
|
tiles[bombRow][bombCol] = null;
|
2024-08-16 16:09:24 +00:00
|
|
|
}
|
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
// Magic cube
|
2024-08-05 08:04:54 +00:00
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
void _createMagicCube(int row, int col) async {
|
|
|
|
isSoundPlaying = await MainDB.instance.getSoundEnabled();
|
|
|
|
if (isSoundPlaying) {
|
|
|
|
AudioManager.playFourElementsSound();
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
2024-09-27 13:40:14 +00:00
|
|
|
var tile = tiles[row][col];
|
|
|
|
tile?.sprite = Tile.magicCubeSprite;
|
|
|
|
tile?.isMagicCube = true;
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|
2024-09-04 12:11:40 +00:00
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
// Game over
|
2024-09-04 12:11:40 +00:00
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
void _onGameOver() {
|
|
|
|
print("Game Over! Score: ${score}");
|
|
|
|
showGameOverScreen();
|
2024-09-04 12:11:40 +00:00
|
|
|
}
|
|
|
|
|
2024-09-27 13:40:14 +00:00
|
|
|
void showGameOverScreen() {
|
|
|
|
isGameOver = true;
|
|
|
|
remove(_playerScore);
|
|
|
|
if (gameModeManager.currentMode) {
|
|
|
|
remove(_playerMoves);
|
|
|
|
} else {
|
|
|
|
remove(_remainingTime);
|
2024-09-04 12:11:40 +00:00
|
|
|
}
|
2024-09-27 13:40:14 +00:00
|
|
|
|
|
|
|
this.overlays.remove(PauseButton.id);
|
|
|
|
this.overlays.remove(RestartButton.id);
|
|
|
|
this.overlays.remove(HintButton.id);
|
|
|
|
MainDB.instance.addHighScore(score, () {
|
|
|
|
this.overlays.add(GameOverMenu.id);
|
|
|
|
});
|
2024-09-04 12:11:40 +00:00
|
|
|
}
|
2024-08-05 08:04:54 +00:00
|
|
|
}
|