match-three/lib/game/components/grid_component.dart
savinmax 3f12ce8d3f Add dynamic grid sizing support for levels
- Add gridWidth and gridHeight properties to level configuration
- Update GameGrid to accept custom dimensions instead of using constants
- Modify GridComponent to calculate gem size based on grid dimensions
- Update MatchThreeGame constructor to pass grid dimensions
- Ensure proper scaling and positioning for variable grid sizes
2025-09-21 18:06:00 +02:00

199 lines
5.9 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;
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, this.gridHeight, this.gridWidth)
: super();
@override
Future<void> onLoad() async {
// 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() {
// 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 < gridHeight; row++) {
gemComponents.add(<GemComponent?>[]);
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 * gemSize + GameConstants.gridPadding,
row * 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
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<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 = 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
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 * gemSize + GameConstants.gridPadding,
fallToRow * 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 * gemSize + GameConstants.gridPadding,
gem.row * gemSize + GameConstants.gridPadding,
);
gemComponent.position.x = targetPosition.x;
gemComponent.position.y = -gemSize;
add(gemComponent);
effects.add(gemComponent.animateFall(targetPosition));
}
await Future.wait(effects);
}
void onGemTapped(Gem gem) {
if (canInterract) {
game.selectGem(gem);
}
}
}