match-three/lib/game/models/level.dart
savinmax ea3f0c4e18 Add level system with progression and selection screen
- Add Level model with constraints, objectives, and star ratings
- Create LevelService for loading levels from JSON configuration
- Implement LevelSelectionScreen with visual progress tracking
- Update GameBloc to handle level-based gameplay
- Add 10 predefined levels with varying difficulty and objectives
- Integrate level progression system into game flow
2025-09-21 17:08:44 +02:00

279 lines
6.5 KiB
Dart

import 'package:equatable/equatable.dart';
import 'package:match_three/game/models/gem.dart';
enum StarCriteria {
SCORE,
MOVES_REMAINING,
TIME_REMAINING,
EFFICIENCY,
COMBINED,
}
class Level extends Equatable {
final int id;
final String name;
final String description;
final LevelConstraints constraints;
final LevelObjectives objectives;
final StarRating starRating;
final List<int> availableGemTypes;
final bool unlocked;
const Level({
required this.id,
required this.name,
required this.description,
required this.constraints,
required this.objectives,
required this.starRating,
required this.availableGemTypes,
this.unlocked = false,
});
factory Level.fromJson(Map<String, dynamic> json) {
return Level(
id: json['id'],
name: json['name'],
description: json['description'],
constraints: LevelConstraints.fromJson(json['constraints']),
objectives: LevelObjectives.fromJson(json['objectives']),
starRating: StarRating.fromJson(json['starRating']),
availableGemTypes: List<int>.from(json['availableGemTypes']),
unlocked: json['unlocked'] ?? false,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'description': description,
'constraints': constraints.toJson(),
'objectives': objectives.toJson(),
'starRating': starRating.toJson(),
'availableGemTypes': availableGemTypes,
'unlocked': unlocked,
};
}
Level copyWith({
int? id,
String? name,
String? description,
LevelConstraints? constraints,
LevelObjectives? objectives,
StarRating? starRating,
List<int>? availableGemTypes,
bool? unlocked,
}) {
return Level(
id: id ?? this.id,
name: name ?? this.name,
description: description ?? this.description,
constraints: constraints ?? this.constraints,
objectives: objectives ?? this.objectives,
starRating: starRating ?? this.starRating,
availableGemTypes: availableGemTypes ?? this.availableGemTypes,
unlocked: unlocked ?? this.unlocked,
);
}
@override
List<Object?> get props => [
id,
name,
description,
constraints,
objectives,
starRating,
availableGemTypes,
unlocked,
];
}
class LevelConstraints extends Equatable {
final int? targetScore;
final int? maxMoves;
final int? timeLimit; // in seconds
const LevelConstraints({
this.targetScore,
this.maxMoves,
this.timeLimit,
});
factory LevelConstraints.fromJson(Map<String, dynamic> json) {
return LevelConstraints(
targetScore: json['targetScore'],
maxMoves: json['maxMoves'],
timeLimit: json['timeLimit'],
);
}
Map<String, dynamic> toJson() {
return {
'targetScore': targetScore,
'maxMoves': maxMoves,
'timeLimit': timeLimit,
};
}
bool get hasTimeLimit => timeLimit != null;
bool get hasMoveLimit => maxMoves != null;
bool get hasScoreTarget => targetScore != null;
@override
List<Object?> get props => [targetScore, maxMoves, timeLimit];
}
class LevelObjectives extends Equatable {
final Map<int, int> clearGemTypes; // gemType -> count required
const LevelObjectives({
this.clearGemTypes = const {},
});
factory LevelObjectives.fromJson(Map<String, dynamic> json) {
Map<int, int> clearGemTypes = {};
if (json['clearGemTypes'] != null) {
final clearGemTypesJson = json['clearGemTypes'] as Map<String, dynamic>;
clearGemTypes = clearGemTypesJson.map(
(key, value) => MapEntry(int.parse(key), value as int),
);
}
return LevelObjectives(
clearGemTypes: clearGemTypes,
);
}
Map<String, dynamic> toJson() {
return {
'clearGemTypes': clearGemTypes.map(
(key, value) => MapEntry(key.toString(), value),
),
};
}
bool get hasGemTypeObjectives => clearGemTypes.isNotEmpty;
@override
List<Object?> get props => [clearGemTypes];
}
class StarRating extends Equatable {
final StarCriteria criteria;
final StarThresholds thresholds;
const StarRating({
required this.criteria,
required this.thresholds,
});
factory StarRating.fromJson(Map<String, dynamic> json) {
return StarRating(
criteria: StarCriteria.values.firstWhere(
(e) => e.name == json['criteria'],
),
thresholds: StarThresholds.fromJson(json['thresholds']),
);
}
Map<String, dynamic> toJson() {
return {
'criteria': criteria.name,
'thresholds': thresholds.toJson(),
};
}
@override
List<Object?> get props => [criteria, thresholds];
}
class StarThresholds extends Equatable {
final int oneStar;
final int twoStar;
final int threeStar;
const StarThresholds({
required this.oneStar,
required this.twoStar,
required this.threeStar,
});
factory StarThresholds.fromJson(Map<String, dynamic> json) {
return StarThresholds(
oneStar: json['oneStar'],
twoStar: json['twoStar'],
threeStar: json['threeStar'],
);
}
Map<String, dynamic> toJson() {
return {
'oneStar': oneStar,
'twoStar': twoStar,
'threeStar': threeStar,
};
}
@override
List<Object?> get props => [oneStar, twoStar, threeStar];
}
class LevelProgress extends Equatable {
final int levelId;
final bool completed;
final int stars;
final int bestScore;
final bool unlocked;
const LevelProgress({
required this.levelId,
this.completed = false,
this.stars = 0,
this.bestScore = 0,
this.unlocked = false,
});
factory LevelProgress.fromJson(Map<String, dynamic> json) {
return LevelProgress(
levelId: json['levelId'],
completed: json['completed'] ?? false,
stars: json['stars'] ?? 0,
bestScore: json['bestScore'] ?? 0,
unlocked: json['unlocked'] ?? false,
);
}
Map<String, dynamic> toJson() {
return {
'levelId': levelId,
'completed': completed,
'stars': stars,
'bestScore': bestScore,
'unlocked': unlocked,
};
}
LevelProgress copyWith({
int? levelId,
bool? completed,
int? stars,
int? bestScore,
bool? unlocked,
}) {
return LevelProgress(
levelId: levelId ?? this.levelId,
completed: completed ?? this.completed,
stars: stars ?? this.stars,
bestScore: bestScore ?? this.bestScore,
unlocked: unlocked ?? this.unlocked,
);
}
@override
List<Object?> get props => [levelId, completed, stars, bestScore, unlocked];
}