Adding Flame
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
14
lib/flame_components/sprites.dart
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import 'package:flame/flame.dart';
|
||||||
|
|
||||||
|
Future<void> loadImages() async {
|
||||||
|
final imageNames = [
|
||||||
|
'crystal1.png',
|
||||||
|
'crystal2.png',
|
||||||
|
'crystal3.png',
|
||||||
|
'crystal4.png',
|
||||||
|
'crystal5.png',
|
||||||
|
'crystal6.png',
|
||||||
|
'crystal7.png',
|
||||||
|
];
|
||||||
|
await Future.wait(imageNames.map((name) => Flame.images.load(name)));
|
||||||
|
}
|
117
lib/game/match_magic_game.dart
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/events.dart';
|
||||||
|
import 'package:flame/flame.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:match_magic/models/game_state.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
class MatchMagicGame extends FlameGame with TapDetector {
|
||||||
|
final GameState gameState;
|
||||||
|
|
||||||
|
MatchMagicGame(this.gameState);
|
||||||
|
|
||||||
|
late double tileSize;
|
||||||
|
late double gridWidth;
|
||||||
|
late double gridHeight;
|
||||||
|
late double gridOffsetX;
|
||||||
|
late double gridOffsetY;
|
||||||
|
final Map<String, Sprite> sprites = {};
|
||||||
|
late SpriteComponent selectedTile;
|
||||||
|
bool hasSelectedTile = false;
|
||||||
|
final selectedPaint = Paint()..color = Colors.white.withOpacity(0.5);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
await loadImages();
|
||||||
|
loadGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadImages() async {
|
||||||
|
final imageNames = [
|
||||||
|
'crystal1.png',
|
||||||
|
'crystal2.png',
|
||||||
|
'crystal3.png',
|
||||||
|
'crystal4.png',
|
||||||
|
'crystal5.png',
|
||||||
|
'crystal6.png',
|
||||||
|
'crystal0.png',
|
||||||
|
];
|
||||||
|
for (var name in imageNames) {
|
||||||
|
final sprite = Sprite(await Flame.images.load(name));
|
||||||
|
sprites[name] = sprite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadGrid() {
|
||||||
|
tileSize = size.x / gameState.cols;
|
||||||
|
gridWidth = tileSize * gameState.cols;
|
||||||
|
gridHeight = tileSize * gameState.rows;
|
||||||
|
|
||||||
|
gridOffsetX = (size.x - gridWidth) / 2;
|
||||||
|
gridOffsetY = (size.y - gridHeight) / 2;
|
||||||
|
|
||||||
|
addGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addGrid() {
|
||||||
|
children
|
||||||
|
.whereType<SpriteComponent>()
|
||||||
|
.forEach((component) => component.removeFromParent());
|
||||||
|
|
||||||
|
for (var row = 0; row < gameState.rows; row++) {
|
||||||
|
for (var col = 0; col < gameState.cols; col++) {
|
||||||
|
final value = gameState.grid[row][col];
|
||||||
|
final spriteName = 'crystal$value.png';
|
||||||
|
final sprite = sprites[spriteName];
|
||||||
|
final spriteComponent = SpriteComponent()
|
||||||
|
..sprite = sprite
|
||||||
|
..width = tileSize
|
||||||
|
..height = tileSize
|
||||||
|
..x = gridOffsetX + col * tileSize
|
||||||
|
..y = gridOffsetY + row * tileSize;
|
||||||
|
add(spriteComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
super.render(canvas);
|
||||||
|
if (hasSelectedTile) {
|
||||||
|
canvas.drawRect(
|
||||||
|
Rect.fromLTWH(
|
||||||
|
selectedTile.x,
|
||||||
|
selectedTile.y,
|
||||||
|
tileSize,
|
||||||
|
tileSize,
|
||||||
|
),
|
||||||
|
selectedPaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTapDown(TapDownInfo info) {
|
||||||
|
final x = info.eventPosition.global.x;
|
||||||
|
final y = info.eventPosition.global.y;
|
||||||
|
final col = ((x - gridOffsetX) / tileSize).floor();
|
||||||
|
final row = ((y - gridOffsetY) / tileSize).floor();
|
||||||
|
|
||||||
|
if (col >= 0 && col < gameState.cols && row >= 0 && row < gameState.rows) {
|
||||||
|
if (hasSelectedTile) {
|
||||||
|
gameState.selectElement(row, col);
|
||||||
|
hasSelectedTile = false;
|
||||||
|
addGrid();
|
||||||
|
} else {
|
||||||
|
selectedTile = SpriteComponent()
|
||||||
|
..x = gridOffsetX + col * tileSize
|
||||||
|
..y = gridOffsetY + row * tileSize
|
||||||
|
..width = tileSize
|
||||||
|
..height = tileSize;
|
||||||
|
hasSelectedTile = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:match_magic/screen/game_screen.dart';
|
import 'package:match_magic/screen/game_screen.dart';
|
||||||
import 'package:match_magic/models/game_state.dart';
|
import 'package:match_magic/models/game_state.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(
|
runApp(
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class GameState extends ChangeNotifier {
|
class GameState extends ChangeNotifier {
|
||||||
@ -8,7 +7,6 @@ class GameState extends ChangeNotifier {
|
|||||||
late List<List<int>> grid;
|
late List<List<int>> grid;
|
||||||
int? selectedRow;
|
int? selectedRow;
|
||||||
int? selectedCol;
|
int? selectedCol;
|
||||||
bool isCheckingMatches = false;
|
|
||||||
|
|
||||||
GameState() {
|
GameState() {
|
||||||
_initializeGrid();
|
_initializeGrid();
|
||||||
@ -21,7 +19,7 @@ class GameState extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int _randomElement() {
|
int _randomElement() {
|
||||||
return Random().nextInt(7) + 1;
|
return Random().nextInt(6) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _removeInitialMatches() {
|
void _removeInitialMatches() {
|
||||||
@ -48,7 +46,6 @@ class GameState extends ChangeNotifier {
|
|||||||
swapElements(selectedRow!, selectedCol!, row, col);
|
swapElements(selectedRow!, selectedCol!, row, col);
|
||||||
Future.delayed(const Duration(milliseconds: 300), () {
|
Future.delayed(const Duration(milliseconds: 300), () {
|
||||||
if (!_checkMatches()) {
|
if (!_checkMatches()) {
|
||||||
// Swap back if no match
|
|
||||||
swapElements(row, col, selectedRow!, selectedCol!);
|
swapElements(row, col, selectedRow!, selectedCol!);
|
||||||
}
|
}
|
||||||
selectedRow = null;
|
selectedRow = null;
|
||||||
@ -92,7 +89,7 @@ class GameState extends ChangeNotifier {
|
|||||||
_applyGravity();
|
_applyGravity();
|
||||||
Future.delayed(const Duration(milliseconds: 300), () {
|
Future.delayed(const Duration(milliseconds: 300), () {
|
||||||
_fillEmptySpaces();
|
_fillEmptySpaces();
|
||||||
_checkMatches(); // Check again for new matches
|
_checkMatches();
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -103,54 +100,34 @@ class GameState extends ChangeNotifier {
|
|||||||
final value = grid[row][col];
|
final value = grid[row][col];
|
||||||
if (value == 0) return false;
|
if (value == 0) return false;
|
||||||
|
|
||||||
// Check horizontal match
|
|
||||||
int count = 1;
|
int count = 1;
|
||||||
for (int i = col + 1; i < cols && grid[row][i] == value; i++) {
|
for (int i = col + 1; i < cols && grid[row][i] == value; i++) count++;
|
||||||
count++;
|
for (int i = col - 1; i >= 0 && grid[row][i] == value; i--) count++;
|
||||||
}
|
|
||||||
for (int i = col - 1; i >= 0 && grid[row][i] == value; i--) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
if (count >= 3) return true;
|
if (count >= 3) return true;
|
||||||
|
|
||||||
// Check vertical match
|
|
||||||
count = 1;
|
count = 1;
|
||||||
for (int i = row + 1; i < rows && grid[i][col] == value; i++) {
|
for (int i = row + 1; i < rows && grid[i][col] == value; i++) count++;
|
||||||
count++;
|
for (int i = row - 1; i >= 0 && grid[i][col] == value; i--) count++;
|
||||||
}
|
|
||||||
for (int i = row - 1; i >= 0 && grid[i][col] == value; i--) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
return count >= 3;
|
return count >= 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _removeMatchedElements(int row, int col) {
|
void _removeMatchedElements(int row, int col) {
|
||||||
final value = grid[row][col];
|
final value = grid[row][col];
|
||||||
|
|
||||||
// Remove horizontal matches
|
|
||||||
int left = col;
|
int left = col;
|
||||||
while (left > 0 && grid[row][left - 1] == value) {
|
while (left > 0 && grid[row][left - 1] == value) left--;
|
||||||
left--;
|
|
||||||
}
|
|
||||||
int right = col;
|
int right = col;
|
||||||
while (right < cols - 1 && grid[row][right + 1] == value) {
|
while (right < cols - 1 && grid[row][right + 1] == value) right++;
|
||||||
right++;
|
|
||||||
}
|
|
||||||
if (right - left + 1 >= 3) {
|
if (right - left + 1 >= 3) {
|
||||||
for (int i = left; i <= right; i++) {
|
for (int i = left; i <= right; i++) {
|
||||||
grid[row][i] = 0;
|
grid[row][i] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove vertical matches
|
|
||||||
int top = row;
|
int top = row;
|
||||||
while (top > 0 && grid[top - 1][col] == value) {
|
while (top > 0 && grid[top - 1][col] == value) top--;
|
||||||
top--;
|
|
||||||
}
|
|
||||||
int bottom = row;
|
int bottom = row;
|
||||||
while (bottom < rows - 1 && grid[bottom + 1][col] == value) {
|
while (bottom < rows - 1 && grid[bottom + 1][col] == value) bottom++;
|
||||||
bottom++;
|
|
||||||
}
|
|
||||||
if (bottom - top + 1 >= 3) {
|
if (bottom - top + 1 >= 3) {
|
||||||
for (int i = top; i <= bottom; i++) {
|
for (int i = top; i <= bottom; i++) {
|
||||||
grid[i][col] = 0;
|
grid[i][col] = 0;
|
||||||
@ -178,9 +155,9 @@ class GameState extends ChangeNotifier {
|
|||||||
for (int row = rows - 1; row >= 0; row--) {
|
for (int row = rows - 1; row >= 0; row--) {
|
||||||
if (grid[row][col] == 0) {
|
if (grid[row][col] == 0) {
|
||||||
grid[row][col] = _randomElement();
|
grid[row][col] = _randomElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import 'package:flame/game.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:match_magic/models/game_state.dart';
|
import 'package:match_magic/models/game_state.dart';
|
||||||
|
import 'package:match_magic/game/match_magic_game.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class GameScreen extends StatelessWidget {
|
class GameScreen extends StatelessWidget {
|
||||||
@ -7,66 +9,11 @@ class GameScreen extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final gameState = context.watch<GameState>();
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
body: Consumer<GameState>(
|
||||||
title: const Text('Match Magic'),
|
builder: (context, gameState, child) {
|
||||||
),
|
return GameWidget<MatchMagicGame>(
|
||||||
body: LayoutBuilder(
|
game: MatchMagicGame(gameState),
|
||||||
builder: (context, constraints) {
|
|
||||||
final tileSize = constraints.maxWidth / gameState.cols;
|
|
||||||
|
|
||||||
return Center(
|
|
||||||
child: Container(
|
|
||||||
width: constraints.maxWidth,
|
|
||||||
height: constraints.maxWidth,
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
for (int row = 0; row < gameState.rows; row++)
|
|
||||||
for (int col = 0; col < gameState.cols; col++)
|
|
||||||
AnimatedPositioned(
|
|
||||||
key: ValueKey('${row}_$col${gameState.grid[row][col]}'),
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
left: col * tileSize,
|
|
||||||
top: row * tileSize,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
gameState.selectElement(row, col);
|
|
||||||
},
|
|
||||||
child: AnimatedContainer(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
width: tileSize - 4,
|
|
||||||
height: tileSize - 4,
|
|
||||||
margin: const EdgeInsets.all(2.0),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: gameState.selectedRow == row &&
|
|
||||||
gameState.selectedCol == col
|
|
||||||
? Border.all(color: Colors.red, width: 2.0)
|
|
||||||
: null,
|
|
||||||
color: Colors.blue[100],
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: AnimatedOpacity(
|
|
||||||
opacity:
|
|
||||||
gameState.grid[row][col] != 0 ? 1.0 : 0.0,
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: gameState.grid[row][col] != 0
|
|
||||||
? Image.asset(
|
|
||||||
'assets/images/diamond${gameState.grid[row][col]}.png',
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
width: tileSize - 4,
|
|
||||||
height: tileSize - 4,
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
18
pubspec.lock
@ -57,6 +57,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
flame:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flame
|
||||||
|
sha256: "79133dc46a3ff870950f41d0dc1598414e7bd7ae2c29bd9f0a9de208d9a70cb7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.18.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -139,6 +147,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
ordered_set:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ordered_set
|
||||||
|
sha256: "1bfaaaee0419e43ecc9eaebd410eb4bd5039657b72011de75ff3e2915c9aac60"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.3"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -226,4 +242,4 @@ packages:
|
|||||||
version: "14.2.1"
|
version: "14.2.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.4.3 <4.0.0"
|
dart: ">=3.4.3 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.22.0"
|
||||||
|
15
pubspec.yaml
@ -36,6 +36,7 @@ dependencies:
|
|||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.6
|
cupertino_icons: ^1.0.6
|
||||||
|
flame: ^1.18.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@ -61,13 +62,13 @@ flutter:
|
|||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
assets:
|
assets:
|
||||||
- assets/images/diamond1.png
|
- assets/images/crystal1.png
|
||||||
- assets/images/diamond2.png
|
- assets/images/crystal2.png
|
||||||
- assets/images/diamond3.png
|
- assets/images/crystal3.png
|
||||||
- assets/images/diamond4.png
|
- assets/images/crystal4.png
|
||||||
- assets/images/diamond5.png
|
- assets/images/crystal5.png
|
||||||
- assets/images/diamond6.png
|
- assets/images/crystal6.png
|
||||||
- assets/images/diamond7.png
|
- assets/images/crystal0.png
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|