import 'package:flame/components.dart'; import 'package:match_three/bloc/game_bloc.dart'; import 'package:match_three/game/systems/physics_system.dart'; import '../models/grid.dart'; import '../models/gem.dart'; import '../match_three_game.dart'; import 'gem_component.dart'; import '../../utils/constants.dart'; class GridComponent extends PositionComponent with HasGameReference { late GameGrid gameGrid; final List> gemComponents = []; final MatchThreeGame game; int? levelId; bool canInterract = false; GridComponent(this.game, this.levelId) : super(); @override Future onLoad() async { if (levelId == null) { game.gameBloc.add(StartGame()); } else { game.gameBloc.add(StartLevel(levelId!)); } } void _createGemComponents() { // Clear existing components for (final row in gemComponents) { for (final gemComponent in row) { gemComponent?.removeFromParent(); } row.clear(); } gemComponents.clear(); for (final child in children) { if (child is GemComponent) { child.removeFromParent(); } } for (int row = 0; row < GameConstants.gridHeight; row++) { gemComponents.add([]); for (int col = 0; col < GameConstants.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, ); gemComponents[row].add(gemComponent); add(gemComponent); } else { gemComponents[row].add(null); } } } } void updateGrid(GameState state) async { // Work only with relevant events if (state is! GamePlaying && state is! GameLevelPlaying) { return; } canInterract = false; print("Update event with state ${state.runtimeType.toString()}"); // Handle regular game states if (state is GamePlayingSwap) { await _swapGems( state.grid, state.gem1, state.gem2, ); } if (state is GameLevelSwap) { await _swapGems( state.grid, state.gem1, state.gem2, ); } if (state is GamePlayingMatch) { await _animateMatch(state.grid, state.matches); } if (state is GameLevelMatch) { await _animateMatch(state.grid, state.matches); } if (state is GamePlayingDrop) { await _animateDrop(state.grid); } if (state is GameLevelDrop) { await _animateDrop(state.grid); } if (state is GamePlayingNewGems) { await _animateNewGemsFall(state.grid, state.gems); } if (state is GameLevelNewGems) { await _animateNewGemsFall(state.grid, state.gems); } if (state is GamePlayingIdle || state is GameLevelIdle) { canInterract = true; } if (state is GamePlaying) { gameGrid = state.grid; gameGrid.printGrid(); _createGemComponents(); await Future.delayed(const Duration(milliseconds: 100)); print("Updated state ${state.runtimeType.toString()}"); state.done(); } else { state = state as GameLevelPlaying; gameGrid = state.grid; gameGrid.printGrid(); _createGemComponents(); await Future.delayed(const Duration(milliseconds: 100)); print("Updated state ${state.runtimeType.toString()}"); state.done(); } } GemComponent? _findGemComponent(Gem gem) { return gemComponents[gem.row][gem.col]; } _swapGems(GameGrid newGrid, Gem gem1, Gem gem2) async { final gemComp1 = _findGemComponent(gem1); final gemComp2 = _findGemComponent(gem2); if (gemComp1 == null || gemComp2 == null) { print("!! Gem not found"); return; } await PhysicsSystem.animateSwap(gemComp1, gemComp2); } _animateMatch(GameGrid newGrid, List matches, [int combo = 0]) async { final effects = []; final toRemove = []; // Animate explosion for (final gem in matches) { final gemComponent = _findGemComponent(gem); if (gemComponent != null) { toRemove.add(gemComponent); effects.add(gemComponent.animateMatch()); if (combo > 0) { gemComponent.animateCombo(); } } } await Future.wait(effects); for (final gemComponent in toRemove) { gemComponent.removeFromParent(); } } _animateDrop(GameGrid newGrid) async { final effects = []; for (int col = GameConstants.gridWidth - 1; col >= 0; col--) { for (int row = GameConstants.gridHeight - 1; row >= 0; row--) { final gem = gameGrid.getGem(row, col); // if gem is removed - animate fall effect from top if (gem == null) { int fallToRow = row; while (row >= 0) { final gemAbove = gameGrid.getGem(row, col); final gemComponent = gemComponents[row][col]; if (gemAbove != null) { final targetPosition = Vector2( col * GameConstants.gemSize + GameConstants.gridPadding, fallToRow * GameConstants.gemSize + GameConstants.gridPadding, ); print( "@@ Fall $row, $col -> $fallToRow, $col ${gemComponent == null ? "NULL" : "OK"}"); effects.add(gemComponent?.animateFall(targetPosition)); fallToRow--; } row--; } } } } await Future.wait(effects); } _animateNewGemsFall(GameGrid newGrid, List newGems) async { final effects = []; for (final gem in newGems) { // 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, ); gemComponent.position.x = targetPosition.x; gemComponent.position.y = -GameConstants.gemSize; add(gemComponent); effects.add(gemComponent.animateFall(targetPosition)); } await Future.wait(effects); } void onGemTapped(Gem gem) { if (canInterract) { game.selectGem(gem); } } }