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
This commit is contained in:
parent
64053d1d58
commit
3f12ce8d3f
@ -4,6 +4,8 @@
|
|||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "Getting Started",
|
"name": "Getting Started",
|
||||||
"description": "Learn the basics by reaching the target score",
|
"description": "Learn the basics by reaching the target score",
|
||||||
|
"gridWidth": 8,
|
||||||
|
"gridHeight": 8,
|
||||||
"constraints": {
|
"constraints": {
|
||||||
"targetScore": 1000
|
"targetScore": 1000
|
||||||
},
|
},
|
||||||
@ -23,6 +25,8 @@
|
|||||||
"id": 2,
|
"id": 2,
|
||||||
"name": "Move Master",
|
"name": "Move Master",
|
||||||
"description": "Reach the target score with limited moves",
|
"description": "Reach the target score with limited moves",
|
||||||
|
"gridWidth": 7,
|
||||||
|
"gridHeight": 9,
|
||||||
"constraints": {
|
"constraints": {
|
||||||
"targetScore": 1500,
|
"targetScore": 1500,
|
||||||
"maxMoves": 20
|
"maxMoves": 20
|
||||||
@ -43,6 +47,8 @@
|
|||||||
"id": 3,
|
"id": 3,
|
||||||
"name": "Time Trial",
|
"name": "Time Trial",
|
||||||
"description": "Beat the clock to reach your goal",
|
"description": "Beat the clock to reach your goal",
|
||||||
|
"gridWidth": 10,
|
||||||
|
"gridHeight": 6,
|
||||||
"constraints": {
|
"constraints": {
|
||||||
"targetScore": 2000,
|
"targetScore": 2000,
|
||||||
"timeLimit": 60
|
"timeLimit": 60
|
||||||
@ -63,6 +69,8 @@
|
|||||||
"id": 4,
|
"id": 4,
|
||||||
"name": "Color Focus",
|
"name": "Color Focus",
|
||||||
"description": "Clear specific gem colors while reaching the score",
|
"description": "Clear specific gem colors while reaching the score",
|
||||||
|
"gridWidth": 6,
|
||||||
|
"gridHeight": 10,
|
||||||
"constraints": {
|
"constraints": {
|
||||||
"targetScore": 1800
|
"targetScore": 1800
|
||||||
},
|
},
|
||||||
@ -87,6 +95,8 @@
|
|||||||
"id": 5,
|
"id": 5,
|
||||||
"name": "Ultimate Challenge",
|
"name": "Ultimate Challenge",
|
||||||
"description": "Master all skills in this combined challenge",
|
"description": "Master all skills in this combined challenge",
|
||||||
|
"gridWidth": 9,
|
||||||
|
"gridHeight": 8,
|
||||||
"constraints": {
|
"constraints": {
|
||||||
"targetScore": 2500,
|
"targetScore": 2500,
|
||||||
"maxMoves": 25
|
"maxMoves": 25
|
||||||
|
|||||||
@ -501,7 +501,7 @@ class GameBloc extends Bloc<GameEvent, GameState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final grid = GameGrid();
|
final grid = GameGrid(width: level.gridWidth, height: level.gridHeight);
|
||||||
|
|
||||||
// Start timer if level has time limit
|
// Start timer if level has time limit
|
||||||
if (level.constraints.hasTimeLimit) {
|
if (level.constraints.hasTimeLimit) {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ class GemComponent extends RectangleComponent with TapCallbacks {
|
|||||||
|
|
||||||
GemComponent({required this.gem, required this.gridComponent})
|
GemComponent({required this.gem, required this.gridComponent})
|
||||||
: super(
|
: super(
|
||||||
size: Vector2.all(GameConstants.gemSize - 4),
|
size: Vector2.all(gridComponent.gemSize - 4),
|
||||||
paint: Paint()..color = gemColors[gem.type % gemColors.length],
|
paint: Paint()..color = gemColors[gem.type % gemColors.length],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -12,16 +12,22 @@ class GridComponent extends PositionComponent
|
|||||||
late GameGrid gameGrid;
|
late GameGrid gameGrid;
|
||||||
final List<List<GemComponent?>> gemComponents = [];
|
final List<List<GemComponent?>> gemComponents = [];
|
||||||
final MatchThreeGame game;
|
final MatchThreeGame game;
|
||||||
int? levelId;
|
final int levelId;
|
||||||
bool canInterract = false;
|
bool canInterract = false;
|
||||||
|
double gemSize = GameConstants.gemSize;
|
||||||
|
final int gridWidth; // Default grid width
|
||||||
|
final int gridHeight; // Default grid height
|
||||||
|
|
||||||
GridComponent(this.game, this.levelId) : super();
|
GridComponent(this.game, this.levelId, this.gridHeight, this.gridWidth)
|
||||||
|
: super();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
// Always use StartLevel - Free Play is level ID 0
|
// Calculate dynamic gem size based on available screen space
|
||||||
final id = levelId ?? 0; // Default to Free Play if no level specified
|
// For now, use a reasonable default size that will be updated when we have screen dimensions
|
||||||
game.gameBloc.add(StartLevel(id));
|
gemSize =
|
||||||
|
GameConstants.calculateGemSize(gridWidth, gridHeight, 600.0, 800.0);
|
||||||
|
game.gameBloc.add(StartLevel(levelId));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _createGemComponents() {
|
void _createGemComponents() {
|
||||||
@ -39,15 +45,15 @@ class GridComponent extends PositionComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int row = 0; row < GameConstants.gridHeight; row++) {
|
for (int row = 0; row < gridHeight; row++) {
|
||||||
gemComponents.add(<GemComponent?>[]);
|
gemComponents.add(<GemComponent?>[]);
|
||||||
for (int col = 0; col < GameConstants.gridWidth; col++) {
|
for (int col = 0; col < gridWidth; col++) {
|
||||||
final gem = gameGrid.getGem(row, col);
|
final gem = gameGrid.getGem(row, col);
|
||||||
if (gem != null) {
|
if (gem != null) {
|
||||||
final gemComponent = GemComponent(gem: gem, gridComponent: this);
|
final gemComponent = GemComponent(gem: gem, gridComponent: this);
|
||||||
gemComponent.position = Vector2(
|
gemComponent.position = Vector2(
|
||||||
col * GameConstants.gemSize + GameConstants.gridPadding,
|
col * gemSize + GameConstants.gridPadding,
|
||||||
row * GameConstants.gemSize + GameConstants.gridPadding,
|
row * gemSize + GameConstants.gridPadding,
|
||||||
);
|
);
|
||||||
gemComponents[row].add(gemComponent);
|
gemComponents[row].add(gemComponent);
|
||||||
add(gemComponent);
|
add(gemComponent);
|
||||||
@ -88,13 +94,12 @@ class GridComponent extends PositionComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update grid and create components for all GameLevelPlaying states
|
// Update grid and create components for all GameLevelPlaying states
|
||||||
final levelState = state as GameLevelPlaying;
|
gameGrid = state.grid;
|
||||||
gameGrid = levelState.grid;
|
|
||||||
gameGrid.printGrid();
|
gameGrid.printGrid();
|
||||||
_createGemComponents();
|
_createGemComponents();
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
print("Updated state ${state.runtimeType.toString()}");
|
print("Updated state ${state.runtimeType.toString()}");
|
||||||
levelState.done();
|
state.done();
|
||||||
}
|
}
|
||||||
|
|
||||||
GemComponent? _findGemComponent(Gem gem) {
|
GemComponent? _findGemComponent(Gem gem) {
|
||||||
@ -137,8 +142,8 @@ class GridComponent extends PositionComponent
|
|||||||
_animateDrop(GameGrid newGrid) async {
|
_animateDrop(GameGrid newGrid) async {
|
||||||
final effects = <Future>[];
|
final effects = <Future>[];
|
||||||
|
|
||||||
for (int col = GameConstants.gridWidth - 1; col >= 0; col--) {
|
for (int col = gridWidth - 1; col >= 0; col--) {
|
||||||
for (int row = GameConstants.gridHeight - 1; row >= 0; row--) {
|
for (int row = gridHeight - 1; row >= 0; row--) {
|
||||||
final gem = gameGrid.getGem(row, col);
|
final gem = gameGrid.getGem(row, col);
|
||||||
|
|
||||||
// if gem is removed - animate fall effect from top
|
// if gem is removed - animate fall effect from top
|
||||||
@ -149,8 +154,8 @@ class GridComponent extends PositionComponent
|
|||||||
final gemComponent = gemComponents[row][col];
|
final gemComponent = gemComponents[row][col];
|
||||||
if (gemAbove != null) {
|
if (gemAbove != null) {
|
||||||
final targetPosition = Vector2(
|
final targetPosition = Vector2(
|
||||||
col * GameConstants.gemSize + GameConstants.gridPadding,
|
col * gemSize + GameConstants.gridPadding,
|
||||||
fallToRow * GameConstants.gemSize + GameConstants.gridPadding,
|
fallToRow * gemSize + GameConstants.gridPadding,
|
||||||
);
|
);
|
||||||
print(
|
print(
|
||||||
"@@ Fall $row, $col -> $fallToRow, $col ${gemComponent == null ? "NULL" : "OK"}");
|
"@@ Fall $row, $col -> $fallToRow, $col ${gemComponent == null ? "NULL" : "OK"}");
|
||||||
@ -172,12 +177,12 @@ class GridComponent extends PositionComponent
|
|||||||
// Create gem component from gem and animate falling effect to proper location
|
// Create gem component from gem and animate falling effect to proper location
|
||||||
final gemComponent = GemComponent(gem: gem, gridComponent: this);
|
final gemComponent = GemComponent(gem: gem, gridComponent: this);
|
||||||
final targetPosition = Vector2(
|
final targetPosition = Vector2(
|
||||||
gem.col * GameConstants.gemSize + GameConstants.gridPadding,
|
gem.col * gemSize + GameConstants.gridPadding,
|
||||||
gem.row * GameConstants.gemSize + GameConstants.gridPadding,
|
gem.row * gemSize + GameConstants.gridPadding,
|
||||||
);
|
);
|
||||||
|
|
||||||
gemComponent.position.x = targetPosition.x;
|
gemComponent.position.x = targetPosition.x;
|
||||||
gemComponent.position.y = -GameConstants.gemSize;
|
gemComponent.position.y = -gemSize;
|
||||||
add(gemComponent);
|
add(gemComponent);
|
||||||
effects.add(gemComponent.animateFall(targetPosition));
|
effects.add(gemComponent.animateFall(targetPosition));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,18 +12,24 @@ const gridSize = 8 * 64.0 + 32.0; // 8 gems * 64px + padding
|
|||||||
class MatchThreeGame extends FlameGame {
|
class MatchThreeGame extends FlameGame {
|
||||||
late BackgroundComponent backgroundComponent;
|
late BackgroundComponent backgroundComponent;
|
||||||
late GameBloc gameBloc;
|
late GameBloc gameBloc;
|
||||||
final int? levelId;
|
final int levelId;
|
||||||
|
final int gridHeight;
|
||||||
|
final int gridWidth;
|
||||||
GridComponent? gridComponent;
|
GridComponent? gridComponent;
|
||||||
Gem? selectedGem;
|
Gem? selectedGem;
|
||||||
|
|
||||||
MatchThreeGame(this.levelId) : super();
|
MatchThreeGame(
|
||||||
|
{required this.levelId,
|
||||||
|
required this.gridHeight,
|
||||||
|
required this.gridWidth})
|
||||||
|
: super();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
backgroundComponent = BackgroundComponent();
|
backgroundComponent = BackgroundComponent();
|
||||||
add(backgroundComponent);
|
add(backgroundComponent);
|
||||||
|
|
||||||
gridComponent = GridComponent(this, levelId);
|
gridComponent = GridComponent(this, levelId, gridHeight, gridWidth);
|
||||||
add(gridComponent!);
|
add(gridComponent!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,16 +5,18 @@ import '../../utils/constants.dart';
|
|||||||
class GameGrid {
|
class GameGrid {
|
||||||
late List<List<Gem?>> _grid;
|
late List<List<Gem?>> _grid;
|
||||||
final Random _random = Random();
|
final Random _random = Random();
|
||||||
|
final int width;
|
||||||
|
final int height;
|
||||||
|
|
||||||
GameGrid() {
|
GameGrid({required this.width, required this.height}) {
|
||||||
_initializeGrid();
|
_initializeGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initializeGrid() {
|
void _initializeGrid() {
|
||||||
_grid = List.generate(
|
_grid = List.generate(
|
||||||
GameConstants.gridHeight,
|
height,
|
||||||
(row) => List.generate(
|
(row) => List.generate(
|
||||||
GameConstants.gridWidth,
|
width,
|
||||||
(col) => Gem(
|
(col) => Gem(
|
||||||
type: _random.nextInt(GameConstants.gemTypes.length),
|
type: _random.nextInt(GameConstants.gemTypes.length),
|
||||||
row: row,
|
row: row,
|
||||||
@ -25,20 +27,14 @@ class GameGrid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Gem? getGem(int row, int col) {
|
Gem? getGem(int row, int col) {
|
||||||
if (row < 0 ||
|
if (row < 0 || row >= height || col < 0 || col >= width) {
|
||||||
row >= GameConstants.gridHeight ||
|
|
||||||
col < 0 ||
|
|
||||||
col >= GameConstants.gridWidth) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return _grid[row][col];
|
return _grid[row][col];
|
||||||
}
|
}
|
||||||
|
|
||||||
void setGem(int row, int col, Gem? gem) {
|
void setGem(int row, int col, Gem? gem) {
|
||||||
if (row >= 0 &&
|
if (row >= 0 && row < height && col >= 0 && col < width) {
|
||||||
row < GameConstants.gridHeight &&
|
|
||||||
col >= 0 &&
|
|
||||||
col < GameConstants.gridWidth) {
|
|
||||||
_grid[row][col] = gem;
|
_grid[row][col] = gem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,14 +42,11 @@ class GameGrid {
|
|||||||
List<List<Gem?>> get grid => _grid;
|
List<List<Gem?>> get grid => _grid;
|
||||||
|
|
||||||
bool isValidPosition(int row, int col) {
|
bool isValidPosition(int row, int col) {
|
||||||
return row >= 0 &&
|
return row >= 0 && row < height && col >= 0 && col < width;
|
||||||
row < GameConstants.gridHeight &&
|
|
||||||
col >= 0 &&
|
|
||||||
col < GameConstants.gridWidth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
clone() {
|
||||||
final clonedGrid = GameGrid();
|
final clonedGrid = GameGrid(width: width, height: height);
|
||||||
for (int row = 0; row < _grid.length; row++) {
|
for (int row = 0; row < _grid.length; row++) {
|
||||||
for (int col = 0; col < _grid[row].length; col++) {
|
for (int col = 0; col < _grid[row].length; col++) {
|
||||||
final gem = getGem(row, col);
|
final gem = getGem(row, col);
|
||||||
|
|||||||
@ -18,6 +18,8 @@ class Level extends Equatable {
|
|||||||
final StarRating starRating;
|
final StarRating starRating;
|
||||||
final List<int> availableGemTypes;
|
final List<int> availableGemTypes;
|
||||||
final bool unlocked;
|
final bool unlocked;
|
||||||
|
final int gridWidth;
|
||||||
|
final int gridHeight;
|
||||||
|
|
||||||
const Level({
|
const Level({
|
||||||
required this.id,
|
required this.id,
|
||||||
@ -28,6 +30,9 @@ class Level extends Equatable {
|
|||||||
required this.starRating,
|
required this.starRating,
|
||||||
required this.availableGemTypes,
|
required this.availableGemTypes,
|
||||||
this.unlocked = false,
|
this.unlocked = false,
|
||||||
|
this.gridWidth =
|
||||||
|
9, // Default to current grid size for backward compatibility
|
||||||
|
this.gridHeight = 8,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Level.fromJson(Map<String, dynamic> json) {
|
factory Level.fromJson(Map<String, dynamic> json) {
|
||||||
@ -40,6 +45,8 @@ class Level extends Equatable {
|
|||||||
starRating: StarRating.fromJson(json['starRating']),
|
starRating: StarRating.fromJson(json['starRating']),
|
||||||
availableGemTypes: List<int>.from(json['availableGemTypes']),
|
availableGemTypes: List<int>.from(json['availableGemTypes']),
|
||||||
unlocked: json['unlocked'] ?? false,
|
unlocked: json['unlocked'] ?? false,
|
||||||
|
gridWidth: json['gridWidth'] ?? 9, // Default for backward compatibility
|
||||||
|
gridHeight: json['gridHeight'] ?? 8,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +60,8 @@ class Level extends Equatable {
|
|||||||
'starRating': starRating.toJson(),
|
'starRating': starRating.toJson(),
|
||||||
'availableGemTypes': availableGemTypes,
|
'availableGemTypes': availableGemTypes,
|
||||||
'unlocked': unlocked,
|
'unlocked': unlocked,
|
||||||
|
'gridWidth': gridWidth,
|
||||||
|
'gridHeight': gridHeight,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +74,8 @@ class Level extends Equatable {
|
|||||||
StarRating? starRating,
|
StarRating? starRating,
|
||||||
List<int>? availableGemTypes,
|
List<int>? availableGemTypes,
|
||||||
bool? unlocked,
|
bool? unlocked,
|
||||||
|
int? gridWidth,
|
||||||
|
int? gridHeight,
|
||||||
}) {
|
}) {
|
||||||
return Level(
|
return Level(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@ -75,6 +86,8 @@ class Level extends Equatable {
|
|||||||
starRating: starRating ?? this.starRating,
|
starRating: starRating ?? this.starRating,
|
||||||
availableGemTypes: availableGemTypes ?? this.availableGemTypes,
|
availableGemTypes: availableGemTypes ?? this.availableGemTypes,
|
||||||
unlocked: unlocked ?? this.unlocked,
|
unlocked: unlocked ?? this.unlocked,
|
||||||
|
gridWidth: gridWidth ?? this.gridWidth,
|
||||||
|
gridHeight: gridHeight ?? this.gridHeight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +101,8 @@ class Level extends Equatable {
|
|||||||
starRating,
|
starRating,
|
||||||
availableGemTypes,
|
availableGemTypes,
|
||||||
unlocked,
|
unlocked,
|
||||||
|
gridWidth,
|
||||||
|
gridHeight,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import '../../utils/constants.dart';
|
|||||||
|
|
||||||
class GravitySystem {
|
class GravitySystem {
|
||||||
static void applyGravity(GameGrid grid) {
|
static void applyGravity(GameGrid grid) {
|
||||||
for (int col = 0; col < GameConstants.gridWidth; col++) {
|
for (int col = 0; col < grid.width; col++) {
|
||||||
_dropColumn(grid, col);
|
_dropColumn(grid, col);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -14,8 +14,8 @@ class GravitySystem {
|
|||||||
// Fill empty spaces with new gems
|
// Fill empty spaces with new gems
|
||||||
final random = Random();
|
final random = Random();
|
||||||
final List<Gem> newGems = [];
|
final List<Gem> newGems = [];
|
||||||
for (int row = 0; row < GameConstants.gridHeight; row++) {
|
for (int row = 0; row < grid.height; row++) {
|
||||||
for (int col = 0; col < GameConstants.gridWidth; col++) {
|
for (int col = 0; col < grid.width; col++) {
|
||||||
if (grid.getGem(row, col) == null) {
|
if (grid.getGem(row, col) == null) {
|
||||||
final gem = Gem(
|
final gem = Gem(
|
||||||
row: row,
|
row: row,
|
||||||
@ -34,7 +34,7 @@ class GravitySystem {
|
|||||||
final gems = <Gem>[];
|
final gems = <Gem>[];
|
||||||
|
|
||||||
// Collect non-null gems from bottom to top
|
// Collect non-null gems from bottom to top
|
||||||
for (int row = GameConstants.gridHeight - 1; row >= 0; row--) {
|
for (int row = grid.height - 1; row >= 0; row--) {
|
||||||
final gem = grid.getGem(row, col);
|
final gem = grid.getGem(row, col);
|
||||||
if (gem != null) {
|
if (gem != null) {
|
||||||
gems.add(gem);
|
gems.add(gem);
|
||||||
@ -42,13 +42,13 @@ class GravitySystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear column
|
// Clear column
|
||||||
for (int row = 0; row < GameConstants.gridHeight; row++) {
|
for (int row = 0; row < grid.height; row++) {
|
||||||
grid.setGem(row, col, null);
|
grid.setGem(row, col, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Place gems at bottom
|
// Place gems at bottom
|
||||||
for (int i = 0; i < gems.length; i++) {
|
for (int i = 0; i < gems.length; i++) {
|
||||||
final newRow = GameConstants.gridHeight - 1 - i;
|
final newRow = grid.height - 1 - i;
|
||||||
grid.setGem(newRow, col, gems[i].copyWith(row: newRow, col: col));
|
grid.setGem(newRow, col, gems[i].copyWith(row: newRow, col: col));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,14 +8,14 @@ class MatchDetector {
|
|||||||
|
|
||||||
// Check matches
|
// Check matches
|
||||||
List<Gem> currentMatch = [];
|
List<Gem> currentMatch = [];
|
||||||
for (int row = 0; row < GameConstants.gridHeight; row++) {
|
for (int row = 0; row < grid.height; row++) {
|
||||||
for (int col = 0; col < GameConstants.gridWidth; col++) {
|
for (int col = 0; col < grid.width; col++) {
|
||||||
final gem = grid.getGem(row, col);
|
final gem = grid.getGem(row, col);
|
||||||
if (gem == null || matches.contains(gem)) {
|
if (gem == null || matches.contains(gem)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
currentMatch.add(gem);
|
currentMatch.add(gem);
|
||||||
for (int i = row + 1; i < GameConstants.gridHeight; i++) {
|
for (int i = row + 1; i < grid.height; i++) {
|
||||||
final nextGem = grid.getGem(i, col);
|
final nextGem = grid.getGem(i, col);
|
||||||
if (nextGem == null || nextGem.type != gem.type) {
|
if (nextGem == null || nextGem.type != gem.type) {
|
||||||
break;
|
break;
|
||||||
@ -27,7 +27,7 @@ class MatchDetector {
|
|||||||
}
|
}
|
||||||
currentMatch.clear();
|
currentMatch.clear();
|
||||||
currentMatch.add(gem);
|
currentMatch.add(gem);
|
||||||
for (int i = col + 1; i < GameConstants.gridWidth; i++) {
|
for (int i = col + 1; i < grid.width; i++) {
|
||||||
final nextGem = grid.getGem(row, i);
|
final nextGem = grid.getGem(row, i);
|
||||||
if (nextGem == null || nextGem.type != gem.type) {
|
if (nextGem == null || nextGem.type != gem.type) {
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -5,11 +5,13 @@ import 'package:match_three/game/models/gem.dart';
|
|||||||
import '../game/match_three_game.dart';
|
import '../game/match_three_game.dart';
|
||||||
import '../bloc/game_bloc.dart';
|
import '../bloc/game_bloc.dart';
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
|
||||||
class GameScreen extends StatefulWidget {
|
class GameScreen extends StatefulWidget {
|
||||||
int? levelId;
|
final int levelId;
|
||||||
|
final int gridHeight;
|
||||||
|
final int gridWidth;
|
||||||
|
|
||||||
GameScreen({super.key, this.levelId});
|
const GameScreen(
|
||||||
|
{super.key, this.levelId = 0, this.gridHeight = 5, this.gridWidth = 5});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<GameScreen> createState() => _GameScreenState();
|
State<GameScreen> createState() => _GameScreenState();
|
||||||
@ -21,7 +23,10 @@ class _GameScreenState extends State<GameScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
game = MatchThreeGame(widget.levelId);
|
game = MatchThreeGame(
|
||||||
|
levelId: widget.levelId,
|
||||||
|
gridHeight: widget.gridHeight,
|
||||||
|
gridWidth: widget.gridWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -164,7 +164,7 @@ class _LevelSelectionScreenState extends State<LevelSelectionScreen> {
|
|||||||
final isCompleted = levelWithProgress.isCompleted;
|
final isCompleted = levelWithProgress.isCompleted;
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: isUnlocked ? () => _startLevel(level.id) : null,
|
onTap: isUnlocked ? () => _startLevel(level) : null,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isUnlocked ? Colors.white : Colors.grey.shade400,
|
color: isUnlocked ? Colors.white : Colors.grey.shade400,
|
||||||
@ -291,9 +291,9 @@ class _LevelSelectionScreenState extends State<LevelSelectionScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _startLevel(int levelId) {
|
void _startLevel(Level level) {
|
||||||
// Start the level first
|
// Start the level first
|
||||||
print("Starting Level $levelId");
|
print("Starting Level ${level.id}");
|
||||||
|
|
||||||
// Then navigate to game screen
|
// Then navigate to game screen
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@ -301,7 +301,11 @@ class _LevelSelectionScreenState extends State<LevelSelectionScreen> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => BlocProvider.value(
|
builder: (context) => BlocProvider.value(
|
||||||
value: context.read<GameBloc>(),
|
value: context.read<GameBloc>(),
|
||||||
child: GameScreen(levelId: levelId),
|
child: GameScreen(
|
||||||
|
levelId: level.id,
|
||||||
|
gridHeight: level.gridHeight,
|
||||||
|
gridWidth: level.gridWidth,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).then((_) {
|
).then((_) {
|
||||||
|
|||||||
@ -20,6 +20,8 @@ class LevelService {
|
|||||||
id: freePlayLevelId,
|
id: freePlayLevelId,
|
||||||
name: 'Free Play',
|
name: 'Free Play',
|
||||||
description: 'Play without constraints - match gems and score points!',
|
description: 'Play without constraints - match gems and score points!',
|
||||||
|
gridWidth: 5,
|
||||||
|
gridHeight: 5,
|
||||||
constraints: LevelConstraints(), // No constraints
|
constraints: LevelConstraints(), // No constraints
|
||||||
objectives: LevelObjectives(), // No objectives
|
objectives: LevelObjectives(), // No objectives
|
||||||
starRating: StarRating(
|
starRating: StarRating(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
class GameConstants {
|
class GameConstants {
|
||||||
static const int gridWidth = 9;
|
|
||||||
static const int gridHeight = 8;
|
|
||||||
static const double gemSize = 64.0;
|
static const double gemSize = 64.0;
|
||||||
static const double gridPadding = 16.0;
|
static const double gridPadding = 16.0;
|
||||||
static const int minMatchLength = 3;
|
static const int minMatchLength = 3;
|
||||||
@ -16,4 +16,18 @@ class GameConstants {
|
|||||||
// Scoring
|
// Scoring
|
||||||
static const int baseScore = 100;
|
static const int baseScore = 100;
|
||||||
static const int comboMultiplier = 50;
|
static const int comboMultiplier = 50;
|
||||||
|
|
||||||
|
// Dynamic gem size calculation
|
||||||
|
static double calculateGemSize(int gridWidth, int gridHeight,
|
||||||
|
double availableWidth, double availableHeight) {
|
||||||
|
// Calculate gem size based on available space and grid dimensions
|
||||||
|
final gemSizeByWidth = (availableWidth - (2 * gridPadding)) / gridWidth;
|
||||||
|
final gemSizeByHeight = (availableHeight - (2 * gridPadding)) / gridHeight;
|
||||||
|
|
||||||
|
// Use the smaller dimension to ensure the grid fits in both directions
|
||||||
|
final calculatedSize = math.min(gemSizeByWidth, gemSizeByHeight);
|
||||||
|
|
||||||
|
// Ensure minimum size for playability and maximum size for performance
|
||||||
|
return math.max(32.0, math.min(calculatedSize, 80.0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import 'package:match_three/game/systems/match_detector.dart';
|
|||||||
void main() {
|
void main() {
|
||||||
group('Match Three Game Tests', () {
|
group('Match Three Game Tests', () {
|
||||||
test('Grid initialization creates 8x8 grid', () {
|
test('Grid initialization creates 8x8 grid', () {
|
||||||
final grid = GameGrid();
|
final grid = GameGrid(width: 8, height: 8);
|
||||||
|
|
||||||
for (int row = 0; row < 8; row++) {
|
for (int row = 0; row < 8; row++) {
|
||||||
for (int col = 0; col < 8; col++) {
|
for (int col = 0; col < 8; col++) {
|
||||||
@ -15,8 +15,22 @@ void main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Grid initialization creates custom size grid', () {
|
||||||
|
final grid = GameGrid(width: 5, height: 5);
|
||||||
|
|
||||||
|
for (int row = 0; row < 5; row++) {
|
||||||
|
for (int col = 0; col < 5; col++) {
|
||||||
|
expect(grid.getGem(row, col), isNotNull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that positions outside the grid return null
|
||||||
|
expect(grid.getGem(5, 0), isNull);
|
||||||
|
expect(grid.getGem(0, 5), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
test('Match detector validates adjacent positions', () {
|
test('Match detector validates adjacent positions', () {
|
||||||
final grid = GameGrid();
|
final grid = GameGrid(width: 8, height: 8);
|
||||||
|
|
||||||
// Test adjacent positions (should be valid)
|
// Test adjacent positions (should be valid)
|
||||||
expect(MatchDetector.isValidSwap(grid, 0, 0, 0, 1), isTrue);
|
expect(MatchDetector.isValidSwap(grid, 0, 0, 0, 1), isTrue);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user