diff --git a/lib/bloc/game_bloc.dart b/lib/bloc/game_bloc.dart index 53893d8..32af637 100644 --- a/lib/bloc/game_bloc.dart +++ b/lib/bloc/game_bloc.dart @@ -279,20 +279,78 @@ class GameBloc extends Bloc { 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) { - newGrid.setGem(event.row1, event.col1, - gem2.copyWith(row: event.row1, col: event.col1)); - newGrid.setGem(event.row2, event.col2, - gem1.copyWith(row: event.row2, col: event.col2)); + 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, + gem1.copyWith(row: event.row2, col: event.col2)); - // Emit state for swap animation + // Emit state for swap animation + var done = Completer(); + emit(GameLevelSwap( + grid: newGrid, + level: currentState.level, + score: currentState.score, + moves: currentState.moves, + timeElapsed: currentState.timeElapsed, + gemsCleared: currentState.gemsCleared, + gem1: gem1, + gem2: gem2, + done: done.complete, + )); + + // Wait for swap animation + await done.future; + + // Check for matches + final matches = MatchDetector.findMatches(newGrid); + if (matches.isNotEmpty) { + final newMoves = currentState.moves + 1; + + // Check if move limit exceeded + if (currentState.level.constraints.hasMoveLimit && + newMoves >= currentState.level.constraints.maxMoves!) { + // This is the last move, check if level can be completed + final updatedGemsCleared = + _updateGemsCleared(currentState.gemsCleared, matches); + if (!_levelService.isLevelCompleted( + currentState.level, + currentState.score, + newMoves, + currentState.timeElapsed, + updatedGemsCleared, + )) { + add(FailLevel()); + return; + } + } + + emit(_GameLevelPlayingMatch( + grid: newGrid, + level: currentState.level, + score: currentState.score, + moves: newMoves, + timeElapsed: currentState.timeElapsed, + gemsCleared: currentState.gemsCleared, + matches: matches, + )); + 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(); + emit(GameLevelSwap( grid: newGrid, level: currentState.level, @@ -300,66 +358,23 @@ class GameBloc extends Bloc { moves: currentState.moves, timeElapsed: currentState.timeElapsed, gemsCleared: currentState.gemsCleared, - gem1: gem1, - gem2: gem2, + gem1: gem2, + gem2: gem1, done: done.complete, )); - - // Wait for swap animation + // Wait for swap-back animation await done.future; - // Check for matches - final matches = MatchDetector.findMatches(newGrid); - if (matches.isNotEmpty) { - final newMoves = currentState.moves + 1; - - // Check if move limit exceeded - if (currentState.level.constraints.hasMoveLimit && - newMoves >= currentState.level.constraints.maxMoves!) { - // This is the last move, check if level can be completed - final updatedGemsCleared = - _updateGemsCleared(currentState.gemsCleared, matches); - if (!_levelService.isLevelCompleted( - currentState.level, - currentState.score, - newMoves, - currentState.timeElapsed, - updatedGemsCleared, - )) { - add(FailLevel()); - return; - } - } - - emit(_GameLevelPlayingMatch( - grid: newGrid, - level: currentState.level, - score: currentState.score, - moves: newMoves, - timeElapsed: currentState.timeElapsed, - gemsCleared: currentState.gemsCleared, - matches: matches, - )); - add(ProcessMatches()); - } else { - // Revert swap if no matches - newGrid.setGem(event.row1, event.col1, gem1); - newGrid.setGem(event.row2, event.col2, gem2); - var done = Completer(); - - emit(GameLevelSwap( - grid: newGrid, - level: currentState.level, - score: currentState.score, - moves: currentState.moves, - timeElapsed: currentState.timeElapsed, - gemsCleared: currentState.gemsCleared, - gem1: gem2, - gem2: gem1, - done: done.complete, - )); - } - // Wait for swap animation + 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; } } diff --git a/lib/game/components/gem_component.dart b/lib/game/components/gem_component.dart index 9b990cb..006b3bc 100644 --- a/lib/game/components/gem_component.dart +++ b/lib/game/components/gem_component.dart @@ -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( diff --git a/lib/game/components/particle_system.dart b/lib/game/components/particle_system.dart index a715940..b9055b0 100644 --- a/lib/game/components/particle_system.dart +++ b/lib/game/components/particle_system.dart @@ -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, diff --git a/lib/game/match_three_game.dart b/lib/game/match_three_game.dart index 0557020..98205e7 100644 --- a/lib/game/match_three_game.dart +++ b/lib/game/match_three_game.dart @@ -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'; @@ -51,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) { diff --git a/lib/game/models/grid.dart b/lib/game/models/grid.dart index fecce34..713ffc2 100644 --- a/lib/game/models/grid.dart +++ b/lib/game/models/grid.dart @@ -45,7 +45,7 @@ class GameGrid { return row >= 0 && row < height && col >= 0 && col < width; } - clone() { + 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++) { diff --git a/lib/game/systems/match_detector.dart b/lib/game/systems/match_detector.dart index 6e7ee0a..0ca7ffc 100644 --- a/lib/game/systems/match_detector.dart +++ b/lib/game/systems/match_detector.dart @@ -11,7 +11,7 @@ class MatchDetector { 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); diff --git a/lib/game/systems/physics_system.dart b/lib/game/systems/physics_system.dart index ad76f1b..efe0b84 100644 --- a/lib/game/systems/physics_system.dart +++ b/lib/game/systems/physics_system.dart @@ -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 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) { diff --git a/lib/screens/game_screen.dart b/lib/screens/game_screen.dart index e5499d1..f4ae771 100644 --- a/lib/screens/game_screen.dart +++ b/lib/screens/game_screen.dart @@ -19,6 +19,7 @@ class GameScreen extends StatefulWidget { class _GameScreenState extends State { late MatchThreeGame game; + final bool isDebug = true; @override void initState() { @@ -75,6 +76,16 @@ class _GameScreenState extends State { ), ), 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(