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:provider/provider.dart';
|
||||
import 'package:match_magic/screen/game_screen.dart';
|
||||
import 'package:match_magic/models/game_state.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
void main() {
|
||||
runApp(
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GameState extends ChangeNotifier {
|
||||
@ -8,7 +7,6 @@ class GameState extends ChangeNotifier {
|
||||
late List<List<int>> grid;
|
||||
int? selectedRow;
|
||||
int? selectedCol;
|
||||
bool isCheckingMatches = false;
|
||||
|
||||
GameState() {
|
||||
_initializeGrid();
|
||||
@ -21,7 +19,7 @@ class GameState extends ChangeNotifier {
|
||||
}
|
||||
|
||||
int _randomElement() {
|
||||
return Random().nextInt(7) + 1;
|
||||
return Random().nextInt(6) + 1;
|
||||
}
|
||||
|
||||
void _removeInitialMatches() {
|
||||
@ -48,7 +46,6 @@ class GameState extends ChangeNotifier {
|
||||
swapElements(selectedRow!, selectedCol!, row, col);
|
||||
Future.delayed(const Duration(milliseconds: 300), () {
|
||||
if (!_checkMatches()) {
|
||||
// Swap back if no match
|
||||
swapElements(row, col, selectedRow!, selectedCol!);
|
||||
}
|
||||
selectedRow = null;
|
||||
@ -92,7 +89,7 @@ class GameState extends ChangeNotifier {
|
||||
_applyGravity();
|
||||
Future.delayed(const Duration(milliseconds: 300), () {
|
||||
_fillEmptySpaces();
|
||||
_checkMatches(); // Check again for new matches
|
||||
_checkMatches();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@ -103,54 +100,34 @@ class GameState extends ChangeNotifier {
|
||||
final value = grid[row][col];
|
||||
if (value == 0) return false;
|
||||
|
||||
// Check horizontal match
|
||||
int count = 1;
|
||||
for (int i = col + 1; i < cols && grid[row][i] == value; i++) {
|
||||
count++;
|
||||
}
|
||||
for (int i = col - 1; i >= 0 && grid[row][i] == value; i--) {
|
||||
count++;
|
||||
}
|
||||
for (int i = col + 1; i < cols && grid[row][i] == value; i++) count++;
|
||||
for (int i = col - 1; i >= 0 && grid[row][i] == value; i--) count++;
|
||||
if (count >= 3) return true;
|
||||
|
||||
// Check vertical match
|
||||
count = 1;
|
||||
for (int i = row + 1; i < rows && grid[i][col] == value; i++) {
|
||||
count++;
|
||||
}
|
||||
for (int i = row - 1; i >= 0 && grid[i][col] == value; i--) {
|
||||
count++;
|
||||
}
|
||||
for (int i = row + 1; i < rows && grid[i][col] == value; i++) count++;
|
||||
for (int i = row - 1; i >= 0 && grid[i][col] == value; i--) count++;
|
||||
return count >= 3;
|
||||
}
|
||||
|
||||
void _removeMatchedElements(int row, int col) {
|
||||
final value = grid[row][col];
|
||||
|
||||
// Remove horizontal matches
|
||||
int left = col;
|
||||
while (left > 0 && grid[row][left - 1] == value) {
|
||||
left--;
|
||||
}
|
||||
while (left > 0 && grid[row][left - 1] == value) left--;
|
||||
int right = col;
|
||||
while (right < cols - 1 && grid[row][right + 1] == value) {
|
||||
right++;
|
||||
}
|
||||
while (right < cols - 1 && grid[row][right + 1] == value) right++;
|
||||
if (right - left + 1 >= 3) {
|
||||
for (int i = left; i <= right; i++) {
|
||||
grid[row][i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove vertical matches
|
||||
int top = row;
|
||||
while (top > 0 && grid[top - 1][col] == value) {
|
||||
top--;
|
||||
}
|
||||
while (top > 0 && grid[top - 1][col] == value) top--;
|
||||
int bottom = row;
|
||||
while (bottom < rows - 1 && grid[bottom + 1][col] == value) {
|
||||
bottom++;
|
||||
}
|
||||
while (bottom < rows - 1 && grid[bottom + 1][col] == value) bottom++;
|
||||
if (bottom - top + 1 >= 3) {
|
||||
for (int i = top; i <= bottom; i++) {
|
||||
grid[i][col] = 0;
|
||||
@ -178,9 +155,9 @@ class GameState extends ChangeNotifier {
|
||||
for (int row = rows - 1; row >= 0; row--) {
|
||||
if (grid[row][col] == 0) {
|
||||
grid[row][col] = _randomElement();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:match_magic/models/game_state.dart';
|
||||
import 'package:match_magic/game/match_magic_game.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class GameScreen extends StatelessWidget {
|
||||
@ -7,66 +9,11 @@ class GameScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final gameState = context.watch<GameState>();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Match Magic'),
|
||||
),
|
||||
body: LayoutBuilder(
|
||||
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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: Consumer<GameState>(
|
||||
builder: (context, gameState, child) {
|
||||
return GameWidget<MatchMagicGame>(
|
||||
game: MatchMagicGame(gameState),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
18
pubspec.lock
@ -57,6 +57,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
flame:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flame
|
||||
sha256: "79133dc46a3ff870950f41d0dc1598414e7bd7ae2c29bd9f0a9de208d9a70cb7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -139,6 +147,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -226,4 +242,4 @@ packages:
|
||||
version: "14.2.1"
|
||||
sdks:
|
||||
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.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.6
|
||||
flame: ^1.18.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -61,13 +62,13 @@ flutter:
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/images/diamond1.png
|
||||
- assets/images/diamond2.png
|
||||
- assets/images/diamond3.png
|
||||
- assets/images/diamond4.png
|
||||
- assets/images/diamond5.png
|
||||
- assets/images/diamond6.png
|
||||
- assets/images/diamond7.png
|
||||
- assets/images/crystal1.png
|
||||
- assets/images/crystal2.png
|
||||
- assets/images/crystal3.png
|
||||
- assets/images/crystal4.png
|
||||
- assets/images/crystal5.png
|
||||
- assets/images/crystal6.png
|
||||
- assets/images/crystal0.png
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|