Compare commits
2 Commits
64053d1d58
...
eaa6947e93
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eaa6947e93 | ||
|
|
3f12ce8d3f |
@ -4,6 +4,8 @@
|
||||
"id": 1,
|
||||
"name": "Getting Started",
|
||||
"description": "Learn the basics by reaching the target score",
|
||||
"gridWidth": 8,
|
||||
"gridHeight": 8,
|
||||
"constraints": {
|
||||
"targetScore": 1000
|
||||
},
|
||||
@ -23,6 +25,8 @@
|
||||
"id": 2,
|
||||
"name": "Move Master",
|
||||
"description": "Reach the target score with limited moves",
|
||||
"gridWidth": 7,
|
||||
"gridHeight": 9,
|
||||
"constraints": {
|
||||
"targetScore": 1500,
|
||||
"maxMoves": 20
|
||||
@ -43,6 +47,8 @@
|
||||
"id": 3,
|
||||
"name": "Time Trial",
|
||||
"description": "Beat the clock to reach your goal",
|
||||
"gridWidth": 10,
|
||||
"gridHeight": 6,
|
||||
"constraints": {
|
||||
"targetScore": 2000,
|
||||
"timeLimit": 60
|
||||
@ -63,6 +69,8 @@
|
||||
"id": 4,
|
||||
"name": "Color Focus",
|
||||
"description": "Clear specific gem colors while reaching the score",
|
||||
"gridWidth": 6,
|
||||
"gridHeight": 10,
|
||||
"constraints": {
|
||||
"targetScore": 1800
|
||||
},
|
||||
@ -87,6 +95,8 @@
|
||||
"id": 5,
|
||||
"name": "Ultimate Challenge",
|
||||
"description": "Master all skills in this combined challenge",
|
||||
"gridWidth": 9,
|
||||
"gridHeight": 8,
|
||||
"constraints": {
|
||||
"targetScore": 2500,
|
||||
"maxMoves": 25
|
||||
|
||||
@ -279,13 +279,16 @@ class GameBloc extends Bloc<GameEvent, GameState> {
|
||||
currentState.grid, event.row1, event.col1, event.row2, event.col2)) {
|
||||
return;
|
||||
}
|
||||
final newGrid = currentState.grid.clone();
|
||||
GameGrid newGrid = currentState.grid.clone();
|
||||
|
||||
// Perform swap
|
||||
final gem1 = newGrid.getGem(event.row1, event.col1);
|
||||
final gem2 = newGrid.getGem(event.row2, event.col2);
|
||||
|
||||
if (gem1 != null && gem2 != null) {
|
||||
if (gem1 == null || gem2 == null) {
|
||||
print("Gem1 or Gem2 is null, that should not be so");
|
||||
return;
|
||||
}
|
||||
newGrid.setGem(event.row1, event.col1,
|
||||
gem2.copyWith(row: event.row1, col: event.col1));
|
||||
newGrid.setGem(event.row2, event.col2,
|
||||
@ -343,6 +346,7 @@ class GameBloc extends Bloc<GameEvent, GameState> {
|
||||
add(ProcessMatches());
|
||||
} else {
|
||||
// Revert swap if no matches
|
||||
newGrid = newGrid.clone();
|
||||
newGrid.setGem(event.row1, event.col1, gem1);
|
||||
newGrid.setGem(event.row2, event.col2, gem2);
|
||||
var done = Completer();
|
||||
@ -358,8 +362,19 @@ class GameBloc extends Bloc<GameEvent, GameState> {
|
||||
gem2: gem1,
|
||||
done: done.complete,
|
||||
));
|
||||
}
|
||||
// Wait for swap animation
|
||||
// Wait for swap-back animation
|
||||
await done.future;
|
||||
|
||||
done = Completer();
|
||||
emit(GameLevelIdle(
|
||||
grid: newGrid,
|
||||
level: currentState.level,
|
||||
score: currentState.score,
|
||||
moves: currentState.moves,
|
||||
timeElapsed: currentState.timeElapsed,
|
||||
gemsCleared: currentState.gemsCleared,
|
||||
done: done.complete,
|
||||
));
|
||||
await done.future;
|
||||
}
|
||||
}
|
||||
@ -501,7 +516,7 @@ class GameBloc extends Bloc<GameEvent, GameState> {
|
||||
return;
|
||||
}
|
||||
|
||||
final grid = GameGrid();
|
||||
final grid = GameGrid(width: level.gridWidth, height: level.gridHeight);
|
||||
|
||||
// Start timer if level has time limit
|
||||
if (level.constraints.hasTimeLimit) {
|
||||
|
||||
@ -24,7 +24,7 @@ class GemComponent extends RectangleComponent with TapCallbacks {
|
||||
|
||||
GemComponent({required this.gem, required this.gridComponent})
|
||||
: super(
|
||||
size: Vector2.all(GameConstants.gemSize - 4),
|
||||
size: Vector2.all(gridComponent.gemSize - 4),
|
||||
paint: Paint()..color = gemColors[gem.type % gemColors.length],
|
||||
);
|
||||
|
||||
@ -55,7 +55,7 @@ class GemComponent extends RectangleComponent with TapCallbacks {
|
||||
parent?.add(explosion);
|
||||
|
||||
// Animate gem destruction
|
||||
PhysicsSystem.animateMatch(this);
|
||||
await PhysicsSystem.animateMatch(this);
|
||||
|
||||
// Remove after animation
|
||||
await Future.delayed(
|
||||
|
||||
@ -12,16 +12,22 @@ class GridComponent extends PositionComponent
|
||||
late GameGrid gameGrid;
|
||||
final List<List<GemComponent?>> gemComponents = [];
|
||||
final MatchThreeGame game;
|
||||
int? levelId;
|
||||
final int levelId;
|
||||
bool canInterract = false;
|
||||
double gemSize = GameConstants.gemSize;
|
||||
final int gridWidth; // Default grid width
|
||||
final int gridHeight; // Default grid height
|
||||
|
||||
GridComponent(this.game, this.levelId) : super();
|
||||
GridComponent(this.game, this.levelId, this.gridHeight, this.gridWidth)
|
||||
: super();
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
// Always use StartLevel - Free Play is level ID 0
|
||||
final id = levelId ?? 0; // Default to Free Play if no level specified
|
||||
game.gameBloc.add(StartLevel(id));
|
||||
// Calculate dynamic gem size based on available screen space
|
||||
// For now, use a reasonable default size that will be updated when we have screen dimensions
|
||||
gemSize =
|
||||
GameConstants.calculateGemSize(gridWidth, gridHeight, 600.0, 800.0);
|
||||
game.gameBloc.add(StartLevel(levelId));
|
||||
}
|
||||
|
||||
void _createGemComponents() {
|
||||
@ -39,15 +45,15 @@ class GridComponent extends PositionComponent
|
||||
}
|
||||
}
|
||||
|
||||
for (int row = 0; row < GameConstants.gridHeight; row++) {
|
||||
for (int row = 0; row < gridHeight; row++) {
|
||||
gemComponents.add(<GemComponent?>[]);
|
||||
for (int col = 0; col < GameConstants.gridWidth; col++) {
|
||||
for (int col = 0; col < gridWidth; col++) {
|
||||
final gem = gameGrid.getGem(row, col);
|
||||
if (gem != null) {
|
||||
final gemComponent = GemComponent(gem: gem, gridComponent: this);
|
||||
gemComponent.position = Vector2(
|
||||
col * GameConstants.gemSize + GameConstants.gridPadding,
|
||||
row * GameConstants.gemSize + GameConstants.gridPadding,
|
||||
col * gemSize + GameConstants.gridPadding,
|
||||
row * gemSize + GameConstants.gridPadding,
|
||||
);
|
||||
gemComponents[row].add(gemComponent);
|
||||
add(gemComponent);
|
||||
@ -88,13 +94,12 @@ class GridComponent extends PositionComponent
|
||||
}
|
||||
|
||||
// Update grid and create components for all GameLevelPlaying states
|
||||
final levelState = state as GameLevelPlaying;
|
||||
gameGrid = levelState.grid;
|
||||
gameGrid = state.grid;
|
||||
gameGrid.printGrid();
|
||||
_createGemComponents();
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
print("Updated state ${state.runtimeType.toString()}");
|
||||
levelState.done();
|
||||
state.done();
|
||||
}
|
||||
|
||||
GemComponent? _findGemComponent(Gem gem) {
|
||||
@ -137,8 +142,8 @@ class GridComponent extends PositionComponent
|
||||
_animateDrop(GameGrid newGrid) async {
|
||||
final effects = <Future>[];
|
||||
|
||||
for (int col = GameConstants.gridWidth - 1; col >= 0; col--) {
|
||||
for (int row = GameConstants.gridHeight - 1; row >= 0; row--) {
|
||||
for (int col = gridWidth - 1; col >= 0; col--) {
|
||||
for (int row = gridHeight - 1; row >= 0; row--) {
|
||||
final gem = gameGrid.getGem(row, col);
|
||||
|
||||
// if gem is removed - animate fall effect from top
|
||||
@ -149,8 +154,8 @@ class GridComponent extends PositionComponent
|
||||
final gemComponent = gemComponents[row][col];
|
||||
if (gemAbove != null) {
|
||||
final targetPosition = Vector2(
|
||||
col * GameConstants.gemSize + GameConstants.gridPadding,
|
||||
fallToRow * GameConstants.gemSize + GameConstants.gridPadding,
|
||||
col * gemSize + GameConstants.gridPadding,
|
||||
fallToRow * gemSize + GameConstants.gridPadding,
|
||||
);
|
||||
print(
|
||||
"@@ Fall $row, $col -> $fallToRow, $col ${gemComponent == null ? "NULL" : "OK"}");
|
||||
@ -172,12 +177,12 @@ class GridComponent extends PositionComponent
|
||||
// Create gem component from gem and animate falling effect to proper location
|
||||
final gemComponent = GemComponent(gem: gem, gridComponent: this);
|
||||
final targetPosition = Vector2(
|
||||
gem.col * GameConstants.gemSize + GameConstants.gridPadding,
|
||||
gem.row * GameConstants.gemSize + GameConstants.gridPadding,
|
||||
gem.col * gemSize + GameConstants.gridPadding,
|
||||
gem.row * gemSize + GameConstants.gridPadding,
|
||||
);
|
||||
|
||||
gemComponent.position.x = targetPosition.x;
|
||||
gemComponent.position.y = -GameConstants.gemSize;
|
||||
gemComponent.position.y = -gemSize;
|
||||
add(gemComponent);
|
||||
effects.add(gemComponent.animateFall(targetPosition));
|
||||
}
|
||||
|
||||
@ -9,11 +9,12 @@ class ParticleSystem {
|
||||
return ParticleSystemComponent(
|
||||
priority: 1,
|
||||
particle: Particle.generate(
|
||||
count: 15,
|
||||
count: 50,
|
||||
lifespan: 1.0,
|
||||
generator: (i) => AcceleratedParticle(
|
||||
acceleration: Vector2(0, 200),
|
||||
speed: Vector2.random(Random()) * 100,
|
||||
acceleration:
|
||||
(Vector2.random(Random()) - Vector2.random(Random())) * 300,
|
||||
speed: (Vector2.random(Random()) - Vector2.random(Random())) * 500,
|
||||
position: Vector2.zero(),
|
||||
child: CircleParticle(
|
||||
radius: Random().nextDouble() * 3 + 2,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:match_three/game/components/gem_component.dart';
|
||||
import 'package:match_three/game/systems/match_detector.dart';
|
||||
import 'components/grid_component.dart';
|
||||
import 'components/background_component.dart';
|
||||
@ -12,18 +13,24 @@ const gridSize = 8 * 64.0 + 32.0; // 8 gems * 64px + padding
|
||||
class MatchThreeGame extends FlameGame {
|
||||
late BackgroundComponent backgroundComponent;
|
||||
late GameBloc gameBloc;
|
||||
final int? levelId;
|
||||
final int levelId;
|
||||
final int gridHeight;
|
||||
final int gridWidth;
|
||||
GridComponent? gridComponent;
|
||||
Gem? selectedGem;
|
||||
|
||||
MatchThreeGame(this.levelId) : super();
|
||||
MatchThreeGame(
|
||||
{required this.levelId,
|
||||
required this.gridHeight,
|
||||
required this.gridWidth})
|
||||
: super();
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
backgroundComponent = BackgroundComponent();
|
||||
add(backgroundComponent);
|
||||
|
||||
gridComponent = GridComponent(this, levelId);
|
||||
gridComponent = GridComponent(this, levelId, gridHeight, gridWidth);
|
||||
add(gridComponent!);
|
||||
}
|
||||
|
||||
@ -45,23 +52,32 @@ class MatchThreeGame extends FlameGame {
|
||||
selectedGem = gem;
|
||||
return;
|
||||
}
|
||||
if (MatchDetector.isValidSwap(gridComponent!.gameGrid, selectedGem!.row,
|
||||
selectedGem!.col, gem.row, gem.col)) {
|
||||
var theGem = selectedGem;
|
||||
selectedGem = null;
|
||||
if (MatchDetector.isValidSwap(
|
||||
gridComponent!.gameGrid, theGem!.row, theGem.col, gem.row, gem.col)) {
|
||||
// Attempt swap
|
||||
print(
|
||||
")) Valid swapping ${selectedGem!.row}, ${selectedGem!.col} with ${gem.row}, ${gem.col}");
|
||||
")) Valid swapping ${theGem.row}, ${theGem.col} with ${gem.row}, ${gem.col}");
|
||||
|
||||
gameBloc.add(SwapGems(
|
||||
selectedGem!.row,
|
||||
selectedGem!.col,
|
||||
theGem.row,
|
||||
theGem.col,
|
||||
gem.row,
|
||||
gem.col,
|
||||
));
|
||||
} else {
|
||||
print(
|
||||
"(( Invalid swapping ${selectedGem!.row}, ${selectedGem!.col} with ${gem.row}, ${gem.col}");
|
||||
"(( Invalid swapping ${theGem.row}, ${theGem.col} with ${gem.row}, ${gem.col}");
|
||||
}
|
||||
if (gridComponent == null) {
|
||||
return;
|
||||
}
|
||||
for (var comp in gridComponent!.children) {
|
||||
if (comp is GemComponent && comp.gem != gem) {
|
||||
comp.isSelected = false;
|
||||
}
|
||||
}
|
||||
selectedGem = null;
|
||||
}
|
||||
|
||||
void setGameBloc(GameBloc bloc) {
|
||||
|
||||
@ -5,16 +5,18 @@ import '../../utils/constants.dart';
|
||||
class GameGrid {
|
||||
late List<List<Gem?>> _grid;
|
||||
final Random _random = Random();
|
||||
final int width;
|
||||
final int height;
|
||||
|
||||
GameGrid() {
|
||||
GameGrid({required this.width, required this.height}) {
|
||||
_initializeGrid();
|
||||
}
|
||||
|
||||
void _initializeGrid() {
|
||||
_grid = List.generate(
|
||||
GameConstants.gridHeight,
|
||||
height,
|
||||
(row) => List.generate(
|
||||
GameConstants.gridWidth,
|
||||
width,
|
||||
(col) => Gem(
|
||||
type: _random.nextInt(GameConstants.gemTypes.length),
|
||||
row: row,
|
||||
@ -25,20 +27,14 @@ class GameGrid {
|
||||
}
|
||||
|
||||
Gem? getGem(int row, int col) {
|
||||
if (row < 0 ||
|
||||
row >= GameConstants.gridHeight ||
|
||||
col < 0 ||
|
||||
col >= GameConstants.gridWidth) {
|
||||
if (row < 0 || row >= height || col < 0 || col >= width) {
|
||||
return null;
|
||||
}
|
||||
return _grid[row][col];
|
||||
}
|
||||
|
||||
void setGem(int row, int col, Gem? gem) {
|
||||
if (row >= 0 &&
|
||||
row < GameConstants.gridHeight &&
|
||||
col >= 0 &&
|
||||
col < GameConstants.gridWidth) {
|
||||
if (row >= 0 && row < height && col >= 0 && col < width) {
|
||||
_grid[row][col] = gem;
|
||||
}
|
||||
}
|
||||
@ -46,14 +42,11 @@ class GameGrid {
|
||||
List<List<Gem?>> get grid => _grid;
|
||||
|
||||
bool isValidPosition(int row, int col) {
|
||||
return row >= 0 &&
|
||||
row < GameConstants.gridHeight &&
|
||||
col >= 0 &&
|
||||
col < GameConstants.gridWidth;
|
||||
return row >= 0 && row < height && col >= 0 && col < width;
|
||||
}
|
||||
|
||||
clone() {
|
||||
final clonedGrid = GameGrid();
|
||||
GameGrid clone() {
|
||||
final clonedGrid = GameGrid(width: width, height: height);
|
||||
for (int row = 0; row < _grid.length; row++) {
|
||||
for (int col = 0; col < _grid[row].length; col++) {
|
||||
final gem = getGem(row, col);
|
||||
|
||||
@ -18,6 +18,8 @@ class Level extends Equatable {
|
||||
final StarRating starRating;
|
||||
final List<int> availableGemTypes;
|
||||
final bool unlocked;
|
||||
final int gridWidth;
|
||||
final int gridHeight;
|
||||
|
||||
const Level({
|
||||
required this.id,
|
||||
@ -28,6 +30,9 @@ class Level extends Equatable {
|
||||
required this.starRating,
|
||||
required this.availableGemTypes,
|
||||
this.unlocked = false,
|
||||
this.gridWidth =
|
||||
9, // Default to current grid size for backward compatibility
|
||||
this.gridHeight = 8,
|
||||
});
|
||||
|
||||
factory Level.fromJson(Map<String, dynamic> json) {
|
||||
@ -40,6 +45,8 @@ class Level extends Equatable {
|
||||
starRating: StarRating.fromJson(json['starRating']),
|
||||
availableGemTypes: List<int>.from(json['availableGemTypes']),
|
||||
unlocked: json['unlocked'] ?? false,
|
||||
gridWidth: json['gridWidth'] ?? 9, // Default for backward compatibility
|
||||
gridHeight: json['gridHeight'] ?? 8,
|
||||
);
|
||||
}
|
||||
|
||||
@ -53,6 +60,8 @@ class Level extends Equatable {
|
||||
'starRating': starRating.toJson(),
|
||||
'availableGemTypes': availableGemTypes,
|
||||
'unlocked': unlocked,
|
||||
'gridWidth': gridWidth,
|
||||
'gridHeight': gridHeight,
|
||||
};
|
||||
}
|
||||
|
||||
@ -65,6 +74,8 @@ class Level extends Equatable {
|
||||
StarRating? starRating,
|
||||
List<int>? availableGemTypes,
|
||||
bool? unlocked,
|
||||
int? gridWidth,
|
||||
int? gridHeight,
|
||||
}) {
|
||||
return Level(
|
||||
id: id ?? this.id,
|
||||
@ -75,6 +86,8 @@ class Level extends Equatable {
|
||||
starRating: starRating ?? this.starRating,
|
||||
availableGemTypes: availableGemTypes ?? this.availableGemTypes,
|
||||
unlocked: unlocked ?? this.unlocked,
|
||||
gridWidth: gridWidth ?? this.gridWidth,
|
||||
gridHeight: gridHeight ?? this.gridHeight,
|
||||
);
|
||||
}
|
||||
|
||||
@ -88,6 +101,8 @@ class Level extends Equatable {
|
||||
starRating,
|
||||
availableGemTypes,
|
||||
unlocked,
|
||||
gridWidth,
|
||||
gridHeight,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import '../../utils/constants.dart';
|
||||
|
||||
class GravitySystem {
|
||||
static void applyGravity(GameGrid grid) {
|
||||
for (int col = 0; col < GameConstants.gridWidth; col++) {
|
||||
for (int col = 0; col < grid.width; col++) {
|
||||
_dropColumn(grid, col);
|
||||
}
|
||||
}
|
||||
@ -14,8 +14,8 @@ class GravitySystem {
|
||||
// Fill empty spaces with new gems
|
||||
final random = Random();
|
||||
final List<Gem> newGems = [];
|
||||
for (int row = 0; row < GameConstants.gridHeight; row++) {
|
||||
for (int col = 0; col < GameConstants.gridWidth; col++) {
|
||||
for (int row = 0; row < grid.height; row++) {
|
||||
for (int col = 0; col < grid.width; col++) {
|
||||
if (grid.getGem(row, col) == null) {
|
||||
final gem = Gem(
|
||||
row: row,
|
||||
@ -34,7 +34,7 @@ class GravitySystem {
|
||||
final gems = <Gem>[];
|
||||
|
||||
// Collect non-null gems from bottom to top
|
||||
for (int row = GameConstants.gridHeight - 1; row >= 0; row--) {
|
||||
for (int row = grid.height - 1; row >= 0; row--) {
|
||||
final gem = grid.getGem(row, col);
|
||||
if (gem != null) {
|
||||
gems.add(gem);
|
||||
@ -42,13 +42,13 @@ class GravitySystem {
|
||||
}
|
||||
|
||||
// Clear column
|
||||
for (int row = 0; row < GameConstants.gridHeight; row++) {
|
||||
for (int row = 0; row < grid.height; row++) {
|
||||
grid.setGem(row, col, null);
|
||||
}
|
||||
|
||||
// Place gems at bottom
|
||||
for (int i = 0; i < gems.length; i++) {
|
||||
final newRow = GameConstants.gridHeight - 1 - i;
|
||||
final newRow = grid.height - 1 - i;
|
||||
grid.setGem(newRow, col, gems[i].copyWith(row: newRow, col: col));
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,14 +8,14 @@ class MatchDetector {
|
||||
|
||||
// Check matches
|
||||
List<Gem> currentMatch = [];
|
||||
for (int row = 0; row < GameConstants.gridHeight; row++) {
|
||||
for (int col = 0; col < GameConstants.gridWidth; col++) {
|
||||
for (int row = 0; row < grid.height; row++) {
|
||||
for (int col = 0; col < grid.width; col++) {
|
||||
final gem = grid.getGem(row, col);
|
||||
if (gem == null || matches.contains(gem)) {
|
||||
if (gem == null) {
|
||||
continue;
|
||||
}
|
||||
currentMatch.add(gem);
|
||||
for (int i = row + 1; i < GameConstants.gridHeight; i++) {
|
||||
for (int i = row + 1; i < grid.height; i++) {
|
||||
final nextGem = grid.getGem(i, col);
|
||||
if (nextGem == null || nextGem.type != gem.type) {
|
||||
break;
|
||||
@ -27,7 +27,7 @@ class MatchDetector {
|
||||
}
|
||||
currentMatch.clear();
|
||||
currentMatch.add(gem);
|
||||
for (int i = col + 1; i < GameConstants.gridWidth; i++) {
|
||||
for (int i = col + 1; i < grid.width; i++) {
|
||||
final nextGem = grid.getGem(row, i);
|
||||
if (nextGem == null || nextGem.type != gem.type) {
|
||||
break;
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/effects.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -38,17 +40,23 @@ class PhysicsSystem {
|
||||
await Future.delayed(Duration(milliseconds: (fallTime * 1000).toInt()));
|
||||
}
|
||||
|
||||
static void animateMatch(GemComponent gem) {
|
||||
static Future<void> animateMatch(GemComponent gem) async {
|
||||
final scaleDone = Completer();
|
||||
final opacityDone = Completer();
|
||||
// Scale down and fade out
|
||||
gem.add(ScaleEffect.to(
|
||||
Vector2.zero(),
|
||||
EffectController(duration: GameConstants.matchDuration),
|
||||
onComplete: () => scaleDone.complete(),
|
||||
));
|
||||
|
||||
gem.add(OpacityEffect.to(
|
||||
0.0,
|
||||
EffectController(duration: GameConstants.matchDuration),
|
||||
onComplete: () => opacityDone.complete(),
|
||||
));
|
||||
|
||||
await Future.wait([scaleDone.future, opacityDone.future]);
|
||||
}
|
||||
|
||||
static void animatePop(GemComponent gem) {
|
||||
|
||||
@ -5,11 +5,13 @@ import 'package:match_three/game/models/gem.dart';
|
||||
import '../game/match_three_game.dart';
|
||||
import '../bloc/game_bloc.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class GameScreen extends StatefulWidget {
|
||||
int? levelId;
|
||||
final int levelId;
|
||||
final int gridHeight;
|
||||
final int gridWidth;
|
||||
|
||||
GameScreen({super.key, this.levelId});
|
||||
const GameScreen(
|
||||
{super.key, this.levelId = 0, this.gridHeight = 5, this.gridWidth = 5});
|
||||
|
||||
@override
|
||||
State<GameScreen> createState() => _GameScreenState();
|
||||
@ -17,11 +19,15 @@ class GameScreen extends StatefulWidget {
|
||||
|
||||
class _GameScreenState extends State<GameScreen> {
|
||||
late MatchThreeGame game;
|
||||
final bool isDebug = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
game = MatchThreeGame(widget.levelId);
|
||||
game = MatchThreeGame(
|
||||
levelId: widget.levelId,
|
||||
gridHeight: widget.gridHeight,
|
||||
gridWidth: widget.gridWidth);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -70,6 +76,16 @@ class _GameScreenState extends State<GameScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (isDebug)
|
||||
Text(
|
||||
'State: ${state.runtimeType}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Score: ${state.score}',
|
||||
style: const TextStyle(
|
||||
|
||||
@ -164,7 +164,7 @@ class _LevelSelectionScreenState extends State<LevelSelectionScreen> {
|
||||
final isCompleted = levelWithProgress.isCompleted;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: isUnlocked ? () => _startLevel(level.id) : null,
|
||||
onTap: isUnlocked ? () => _startLevel(level) : null,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isUnlocked ? Colors.white : Colors.grey.shade400,
|
||||
@ -291,9 +291,9 @@ class _LevelSelectionScreenState extends State<LevelSelectionScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _startLevel(int levelId) {
|
||||
void _startLevel(Level level) {
|
||||
// Start the level first
|
||||
print("Starting Level $levelId");
|
||||
print("Starting Level ${level.id}");
|
||||
|
||||
// Then navigate to game screen
|
||||
Navigator.push(
|
||||
@ -301,7 +301,11 @@ class _LevelSelectionScreenState extends State<LevelSelectionScreen> {
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: context.read<GameBloc>(),
|
||||
child: GameScreen(levelId: levelId),
|
||||
child: GameScreen(
|
||||
levelId: level.id,
|
||||
gridHeight: level.gridHeight,
|
||||
gridWidth: level.gridWidth,
|
||||
),
|
||||
),
|
||||
),
|
||||
).then((_) {
|
||||
|
||||
@ -20,6 +20,8 @@ class LevelService {
|
||||
id: freePlayLevelId,
|
||||
name: 'Free Play',
|
||||
description: 'Play without constraints - match gems and score points!',
|
||||
gridWidth: 5,
|
||||
gridHeight: 5,
|
||||
constraints: LevelConstraints(), // No constraints
|
||||
objectives: LevelObjectives(), // No objectives
|
||||
starRating: StarRating(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
class GameConstants {
|
||||
static const int gridWidth = 9;
|
||||
static const int gridHeight = 8;
|
||||
static const double gemSize = 64.0;
|
||||
static const double gridPadding = 16.0;
|
||||
static const int minMatchLength = 3;
|
||||
@ -16,4 +16,18 @@ class GameConstants {
|
||||
// Scoring
|
||||
static const int baseScore = 100;
|
||||
static const int comboMultiplier = 50;
|
||||
|
||||
// Dynamic gem size calculation
|
||||
static double calculateGemSize(int gridWidth, int gridHeight,
|
||||
double availableWidth, double availableHeight) {
|
||||
// Calculate gem size based on available space and grid dimensions
|
||||
final gemSizeByWidth = (availableWidth - (2 * gridPadding)) / gridWidth;
|
||||
final gemSizeByHeight = (availableHeight - (2 * gridPadding)) / gridHeight;
|
||||
|
||||
// Use the smaller dimension to ensure the grid fits in both directions
|
||||
final calculatedSize = math.min(gemSizeByWidth, gemSizeByHeight);
|
||||
|
||||
// Ensure minimum size for playability and maximum size for performance
|
||||
return math.max(32.0, math.min(calculatedSize, 80.0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import 'package:match_three/game/systems/match_detector.dart';
|
||||
void main() {
|
||||
group('Match Three Game Tests', () {
|
||||
test('Grid initialization creates 8x8 grid', () {
|
||||
final grid = GameGrid();
|
||||
final grid = GameGrid(width: 8, height: 8);
|
||||
|
||||
for (int row = 0; row < 8; row++) {
|
||||
for (int col = 0; col < 8; col++) {
|
||||
@ -15,8 +15,22 @@ void main() {
|
||||
}
|
||||
});
|
||||
|
||||
test('Grid initialization creates custom size grid', () {
|
||||
final grid = GameGrid(width: 5, height: 5);
|
||||
|
||||
for (int row = 0; row < 5; row++) {
|
||||
for (int col = 0; col < 5; col++) {
|
||||
expect(grid.getGem(row, col), isNotNull);
|
||||
}
|
||||
}
|
||||
|
||||
// Test that positions outside the grid return null
|
||||
expect(grid.getGem(5, 0), isNull);
|
||||
expect(grid.getGem(0, 5), isNull);
|
||||
});
|
||||
|
||||
test('Match detector validates adjacent positions', () {
|
||||
final grid = GameGrid();
|
||||
final grid = GameGrid(width: 8, height: 8);
|
||||
|
||||
// Test adjacent positions (should be valid)
|
||||
expect(MatchDetector.isValidSwap(grid, 0, 0, 0, 1), isTrue);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user