Adding bombs and magic cubes.

This commit is contained in:
Alex Vasilev 2024-08-16 19:09:24 +03:00
parent c6b5ed65d9
commit 9ecf924a8c
4 changed files with 191 additions and 47 deletions

View File

@ -2,9 +2,9 @@ import 'dart:math';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/effects.dart'; import 'package:flame/effects.dart';
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'tile.dart'; import 'tile.dart';
import 'package:flame/sprite.dart'; import 'package:flame/sprite.dart';
import 'package:provider/provider.dart';
import 'swap_notifier.dart'; import 'swap_notifier.dart';
class Board extends FlameGame { class Board extends FlameGame {
@ -17,6 +17,7 @@ class Board extends FlameGame {
int? selectedRow; int? selectedRow;
int? selectedCol; int? selectedCol;
bool animating = false; bool animating = false;
Tile? lastMovedTile;
Board({required this.sprites, required this.swapNotifier}); Board({required this.sprites, required this.swapNotifier});
@ -64,7 +65,7 @@ class Board extends FlameGame {
} }
int _randomElement() { int _randomElement() {
return Random().nextInt(7); return Random().nextInt(sprites.length);
} }
void _removeInitialMatches() { void _removeInitialMatches() {
@ -97,11 +98,12 @@ class Board extends FlameGame {
} else { } else {
tiles[selectedRow!][selectedCol!]?.deselect(); tiles[selectedRow!][selectedCol!]?.deselect();
if (_isAdjacent(selectedRow!, selectedCol!, row, col)) { if (_isAdjacent(selectedRow!, selectedCol!, row, col)) {
swapTiles(tiles[selectedRow!]![selectedCol!]!, tiles[row]![col]!, true); lastMovedTile = tiles[selectedRow!][selectedCol!];
swapTiles(tiles[selectedRow!][selectedCol!]!, tiles[row][col]!, true);
Future.delayed(const Duration(milliseconds: 300), () { Future.delayed(const Duration(milliseconds: 300), () {
if (!checkMatches()) { if (!checkMatches()) {
swapTiles( swapTiles(
tiles[row]![col]!, tiles[selectedRow!]![selectedCol!]!, true); tiles[row][col]!, tiles[selectedRow!][selectedCol!]!, true);
} }
selectedRow = null; selectedRow = null;
selectedCol = null; selectedCol = null;
@ -138,12 +140,13 @@ class Board extends FlameGame {
if (targetTile != null) { if (targetTile != null) {
animating = true; animating = true;
lastMovedTile = tile;
swapTiles(tile, targetTile, true); swapTiles(tile, targetTile, true);
await Future.delayed(const Duration(milliseconds: 300)); await Future.delayed(const Duration(milliseconds: 300));
if (!checkMatches()) { if (!checkMatches()) {
swapTiles(tile, targetTile!, true); swapTiles(tile, targetTile, true);
} }
animating = false; animating = false;
@ -213,56 +216,86 @@ class Board extends FlameGame {
} }
bool _hasMatch(int row, int col) { bool _hasMatch(int row, int col) {
final value = tiles[row]?[col]?.spriteIndex; final value = tiles[row][col]?.spriteIndex;
int count = 1; int count = 1;
for (int i = col + 1; i < cols && tiles[row]?[i]?.spriteIndex == value; i++) for (int i = col + 1; i < cols && tiles[row][i]?.spriteIndex == value; i++)
count++; count++;
for (int i = col - 1; i >= 0 && tiles[row]?[i]?.spriteIndex == value; i--) for (int i = col - 1; i >= 0 && tiles[row][i]?.spriteIndex == value; i--)
count++; count++;
if (count >= 3) return true; if (count >= 3) return true;
count = 1; count = 1;
for (int i = row + 1; i < rows && tiles[i]?[col]?.spriteIndex == value; i++) for (int i = row + 1; i < rows && tiles[i][col]?.spriteIndex == value; i++)
count++; count++;
for (int i = row - 1; i >= 0 && tiles[i]?[col]?.spriteIndex == value; i--) for (int i = row - 1; i >= 0 && tiles[i][col]?.spriteIndex == value; i--)
count++; count++;
return count >= 3; return count >= 3;
} }
int _removeMatchedElements(int row, int col) { int _removeMatchedElements(int row, int col) {
int score = 0; int score = 0;
final value = tiles[row]?[col]?.spriteIndex; final int? value = tiles[row][col]?.spriteIndex;
bool transformToBomb = false;
int left = col; int left = col;
while (left > 0 && tiles[row]?[left - 1]?.spriteIndex == value) left--; while (left > 0 && tiles[row][left - 1]?.spriteIndex == value) left--;
int right = col; int right = col;
while (right < cols - 1 && tiles[row]?[right + 1]?.spriteIndex == value) while (right < cols - 1 && tiles[row][right + 1]?.spriteIndex == value)
right++; right++;
if (right - left + 1 >= 3) { if (right - left + 1 >= 3) {
int matchLength = right - left + 1; int matchLength = right - left + 1;
score += _calculateScore(matchLength); score += _calculateScore(matchLength);
if (matchLength == 4 &&
lastMovedTile != null &&
lastMovedTile!.row == row &&
lastMovedTile!.col == col) {
transformToBomb = true;
}
for (int i = left; i <= right; i++) { for (int i = left; i <= right; i++) {
if (tiles[row]?[i] != null) { if (tiles[row][i] != null &&
_animateRemoveTile(tiles[row]![i]!); (!transformToBomb || tiles[row][i] != lastMovedTile)) {
tiles[row]![i] = null; _animateRemoveTile(tiles[row][i]!);
tiles[row][i] = null;
} }
} }
} }
int top = row; int top = row;
while (top > 0 && tiles[top - 1]?[col]?.spriteIndex == value) top--; while (top > 0 && tiles[top - 1][col]?.spriteIndex == value) top--;
int bottom = row; int bottom = row;
while (bottom < rows - 1 && tiles[bottom + 1]?[col]?.spriteIndex == value) while (bottom < rows - 1 && tiles[bottom + 1][col]?.spriteIndex == value)
bottom++; bottom++;
if (bottom - top + 1 >= 3) { if (bottom - top + 1 >= 3) {
int matchLength = bottom - top + 1; int matchLength = bottom - top + 1;
score += _calculateScore(matchLength); score += _calculateScore(matchLength);
for (int i = top; i <= bottom; i++) {
if (tiles[i]?[col] != null) { if (matchLength == 4 &&
_animateRemoveTile(tiles[i]![col]!); lastMovedTile != null &&
tiles[i]![col] = null; lastMovedTile!.row == row &&
lastMovedTile!.col == col) {
transformToBomb = true;
} }
for (int i = top; i <= bottom; i++) {
if (tiles[i][col] != null &&
(!transformToBomb || tiles[i][col] != lastMovedTile)) {
_animateRemoveTile(tiles[i][col]!);
tiles[i][col] = null;
}
}
}
if (transformToBomb && lastMovedTile != null) {
_createBomb(lastMovedTile!.row, lastMovedTile!.col);
} else {
if (tiles[row][col] != null) {
_animateRemoveTile(tiles[row][col]!);
tiles[row][col] = null;
} }
} }
@ -274,6 +307,8 @@ class Board extends FlameGame {
return 50; return 50;
} else if (matchLength == 4) { } else if (matchLength == 4) {
return 100; return 100;
} else if (matchLength == 5) {
return 200;
} }
return 0; return 0;
} }
@ -284,16 +319,113 @@ class Board extends FlameGame {
}); });
} }
void _createBomb(int row, int col) {
final tile = tiles[row][col]!;
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;
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() { void _applyGravity() {
for (int col = 0; col < cols; col++) { for (int col = 0; col < cols; col++) {
for (int row = rows - 1; row >= 0; row--) { for (int row = rows - 1; row >= 0; row--) {
if (tiles[row]?[col] == null) { if (tiles[row][col] == null) {
for (int k = row - 1; k >= 0; k--) { for (int k = row - 1; k >= 0; k--) {
if (tiles[k]?[col] != null) { if (tiles[k][col] != null) {
tiles[row]![col] = tiles[k]![col]!; tiles[row][col] = tiles[k][col]!;
tiles[k]![col] = null; tiles[k][col] = null;
tiles[row]![col]!.row = row; tiles[row][col]!.row = row;
tiles[row]![col]!.animateMoveTo( tiles[row][col]!.animateMoveTo(
Vector2(col * tileSize, row * tileSize), () {}); Vector2(col * tileSize, row * tileSize), () {});
break; break;
} }
@ -306,7 +438,7 @@ class Board extends FlameGame {
void _fillEmptySpaces() { void _fillEmptySpaces() {
for (int col = 0; col < cols; col++) { for (int col = 0; col < cols; col++) {
for (int row = rows - 1; row >= 0; row--) { for (int row = rows - 1; row >= 0; row--) {
if (tiles[row]?[col] == null) { if (tiles[row][col] == null) {
int spriteIndex = _randomElement(); int spriteIndex = _randomElement();
var tile = Tile( var tile = Tile(
sprite: sprites[spriteIndex], sprite: sprites[spriteIndex],

View File

@ -2,7 +2,6 @@ import 'package:flame/components.dart';
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:match_magic/game/sprite_loader.dart'; import 'package:match_magic/game/sprite_loader.dart';
import 'package:match_magic/main.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'board.dart'; import 'board.dart';
import 'swap_notifier.dart'; import 'swap_notifier.dart';
@ -48,6 +47,9 @@ class _MatchMagicGameScreenState extends State<MatchMagicGameScreen> {
sprites: sprites, swapNotifier: context.read<SwapNotifier>()); sprites: sprites, swapNotifier: context.read<SwapNotifier>());
return Column( return Column(
children: [ children: [
SizedBox(
height: 50,
),
const ScoreDisplay(), const ScoreDisplay(),
Expanded( Expanded(
child: Center( child: Center(
@ -59,8 +61,14 @@ class _MatchMagicGameScreenState extends State<MatchMagicGameScreen> {
), ),
ElevatedButton( ElevatedButton(
onPressed: _restartGame, onPressed: _restartGame,
child: const Text('Restart'), child: const Text(
'Restart',
style: TextStyle(color: Colors.black),
), ),
),
SizedBox(
height: 50,
)
], ],
); );
} }
@ -72,19 +80,6 @@ class _MatchMagicGameScreenState extends State<MatchMagicGameScreen> {
} }
} }
class MatchMagicGame extends StatelessWidget {
final List<Sprite> sprites;
const MatchMagicGame({required this.sprites, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GameWidget(
game: Board(sprites: sprites, swapNotifier: context.read<SwapNotifier>()),
);
}
}
class ScoreDisplay extends StatelessWidget { class ScoreDisplay extends StatelessWidget {
const ScoreDisplay({Key? key}) : super(key: key); const ScoreDisplay({Key? key}) : super(key: key);
@ -96,7 +91,8 @@ class ScoreDisplay extends StatelessWidget {
builder: (context, notifier, child) { builder: (context, notifier, child) {
return Text( return Text(
'Score: ${notifier.score}', 'Score: ${notifier.score}',
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), style: const TextStyle(
fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white),
); );
}, },
), ),

View File

@ -2,6 +2,7 @@ import 'package:flame/components.dart';
import 'package:flame/effects.dart'; import 'package:flame/effects.dart';
import 'package:flame/events.dart'; import 'package:flame/events.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'board.dart';
class Tile extends SpriteComponent with TapCallbacks, DragCallbacks { class Tile extends SpriteComponent with TapCallbacks, DragCallbacks {
int row; int row;
@ -10,6 +11,8 @@ class Tile extends SpriteComponent with TapCallbacks, DragCallbacks {
final void Function(Tile) onTileTap; final void Function(Tile) onTileTap;
final void Function(Tile, Vector2) onSwipe; final void Function(Tile, Vector2) onSwipe;
bool isSelected = false; bool isSelected = false;
bool isBomb = false;
bool isMagicCube = false;
Vector2? startDragPosition; Vector2? startDragPosition;
Tile({ Tile({
@ -25,18 +28,30 @@ class Tile extends SpriteComponent with TapCallbacks, DragCallbacks {
@override @override
bool onTapDown(TapDownEvent event) { bool onTapDown(TapDownEvent event) {
if (parent is Board && (parent as Board).animating) {
return false;
}
if (isBomb) {
(parent as Board).explodeBomb(this);
} else {
onTileTap(this); onTileTap(this);
}
return true; return true;
} }
@override @override
bool onDragStart(DragStartEvent event) { bool onDragStart(DragStartEvent event) {
super.onDragStart(event);
if (parent is Board && (parent as Board).animating) {
return false;
}
startDragPosition = event.localPosition; startDragPosition = event.localPosition;
return true; return true;
} }
@override @override
bool onDragEnd(DragEndEvent event) { bool onDragEnd(DragEndEvent event) {
super.onDragEnd(event);
if (startDragPosition != null) { if (startDragPosition != null) {
final delta = event.velocity; final delta = event.velocity;
onSwipe(this, delta); onSwipe(this, delta);

View File

@ -1,6 +1,5 @@
import 'package:flame/sprite.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:match_magic/game/sprite_loader.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'game/match_magic_game.dart'; import 'game/match_magic_game.dart';
import 'game/swap_notifier.dart'; import 'game/swap_notifier.dart';
@ -14,6 +13,7 @@ void main() {
child: const MyApp(), child: const MyApp(),
), ),
); );
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@ -27,6 +27,7 @@ class MyApp extends StatelessWidget {
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
), ),
home: const Scaffold( home: const Scaffold(
backgroundColor: Colors.black,
body: MatchMagicGameScreen(), body: MatchMagicGameScreen(),
), ),
); );