match-three/lib/game/components/grid_component.dart
savinmax 64053d1d58 Refactor game state management to use level-based architecture
Remove legacy GamePlaying states and consolidate all gameplay logic
to use the GameLevelPlaying state system. This simplifies state
management by eliminating duplicate code paths and ensures consistent
behavior across all game modes including Free Play.
2025-09-21 17:26:51 +02:00

194 lines
5.8 KiB
Dart

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<MatchThreeGame> {
late GameGrid gameGrid;
final List<List<GemComponent?>> gemComponents = [];
final MatchThreeGame game;
int? levelId;
bool canInterract = false;
GridComponent(this.game, this.levelId) : 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));
}
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(<GemComponent?>[]);
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 level-based states (including Free Play)
if (state is! GameLevelPlaying) {
return;
}
canInterract = false;
print("Update event with state ${state.runtimeType.toString()}");
// Handle level-based states
if (state is GameLevelSwap) {
await _swapGems(
state.grid,
state.gem1,
state.gem2,
);
}
if (state is GameLevelMatch) {
await _animateMatch(state.grid, state.matches);
}
if (state is GameLevelDrop) {
await _animateDrop(state.grid);
}
if (state is GameLevelNewGems) {
await _animateNewGemsFall(state.grid, state.gems);
}
if (state is GameLevelIdle) {
canInterract = true;
}
// Update grid and create components for all GameLevelPlaying states
final levelState = state as GameLevelPlaying;
gameGrid = levelState.grid;
gameGrid.printGrid();
_createGemComponents();
await Future.delayed(const Duration(milliseconds: 100));
print("Updated state ${state.runtimeType.toString()}");
levelState.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<Gem> matches, [int combo = 0]) async {
final effects = <Future>[];
final toRemove = <GemComponent>[];
// 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 = <Future>[];
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<Gem> newGems) async {
final effects = <Future>[];
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);
}
}
}