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.
194 lines
5.8 KiB
Dart
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);
|
|
}
|
|
}
|
|
}
|