Add playable core UI: board painter, drag-and-drop, HUD, result overlay
CustomPainter board with gems/ghost/clear-flash, finger-lifted drag with snap preview, combo text effect, HUD chips, phase overlays with rescue stubs, demo stage. E2E widget test drives a real drag gesture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../game/engine/game_engine.dart';
|
||||
import '../game/engine/piece_generator.dart';
|
||||
import '../game/models/grid.dart';
|
||||
import '../game/models/objective.dart';
|
||||
import '../game/models/piece.dart';
|
||||
import '../game/models/stage.dart';
|
||||
|
||||
/// Immutable snapshot of one engine moment; the only game state the UI sees.
|
||||
class GameViewState {
|
||||
const GameViewState({
|
||||
required this.grid,
|
||||
required this.tray,
|
||||
required this.score,
|
||||
required this.comboStreak,
|
||||
required this.movesLeft,
|
||||
required this.moveLimit,
|
||||
required this.phase,
|
||||
required this.stuckReason,
|
||||
required this.objectives,
|
||||
required this.starsEarned,
|
||||
required this.objectiveProgress,
|
||||
required this.lastPlacement,
|
||||
required this.fxTick,
|
||||
});
|
||||
|
||||
final GridState grid;
|
||||
final List<Piece> tray;
|
||||
final int score;
|
||||
final int comboStreak;
|
||||
final int movesLeft;
|
||||
final int moveLimit;
|
||||
final GamePhase phase;
|
||||
final StuckReason? stuckReason;
|
||||
final List<Objective> objectives;
|
||||
final int starsEarned;
|
||||
final double objectiveProgress;
|
||||
final PlacementResult? lastPlacement;
|
||||
|
||||
/// Increments on every accepted placement so animations can retrigger.
|
||||
final int fxTick;
|
||||
}
|
||||
|
||||
/// Bridges the pure-Dart [GameEngine] to the widget tree. The engine object
|
||||
/// never leaks past this class.
|
||||
class GameSessionNotifier extends Notifier<GameViewState?> {
|
||||
GameEngine? _engine;
|
||||
StageConfig? _stage;
|
||||
PieceGenerator? _generatorOverride;
|
||||
int _attempt = 0;
|
||||
int _fxTick = 0;
|
||||
|
||||
@override
|
||||
GameViewState? build() => null;
|
||||
|
||||
void startStage(StageConfig stage,
|
||||
{int attempt = 0, PieceGenerator? generator}) {
|
||||
_stage = stage;
|
||||
_attempt = attempt;
|
||||
_generatorOverride = generator;
|
||||
_engine = GameEngine(stage, attempt: attempt, generator: generator);
|
||||
_fxTick = 0;
|
||||
_publish(lastPlacement: null);
|
||||
}
|
||||
|
||||
/// Restarts the current stage as a new attempt (fresh piece sequence).
|
||||
void restart() {
|
||||
final stage = _stage;
|
||||
if (stage == null) throw StateError('no stage to restart');
|
||||
startStage(stage, attempt: _attempt + 1, generator: _generatorOverride);
|
||||
}
|
||||
|
||||
bool tryPlace(int trayIndex, int x, int y) {
|
||||
final engine = _engine;
|
||||
if (engine == null) return false;
|
||||
final result = engine.tryPlace(trayIndex, x, y);
|
||||
if (result == null) return false;
|
||||
_fxTick++;
|
||||
_publish(lastPlacement: result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canPlaceAt(int trayIndex, int x, int y) =>
|
||||
_engine?.tryPlaceWouldSucceed(trayIndex, x, y) ?? false;
|
||||
|
||||
void useContinue() {
|
||||
_engine?.useContinue();
|
||||
_publish(lastPlacement: null);
|
||||
}
|
||||
|
||||
void addExtraMoves() {
|
||||
_engine?.addExtraMoves();
|
||||
_publish(lastPlacement: null);
|
||||
}
|
||||
|
||||
void declineAndLose() {
|
||||
_engine?.declineAndLose();
|
||||
_publish(lastPlacement: null);
|
||||
}
|
||||
|
||||
void _publish({required PlacementResult? lastPlacement}) {
|
||||
final engine = _engine!;
|
||||
state = GameViewState(
|
||||
grid: engine.grid,
|
||||
tray: engine.tray,
|
||||
score: engine.score,
|
||||
comboStreak: engine.combo.streak,
|
||||
movesLeft: engine.movesLeft,
|
||||
moveLimit: engine.movesLeft + engine.movesUsed,
|
||||
phase: engine.phase,
|
||||
stuckReason: engine.stuckReason,
|
||||
objectives: engine.objectives,
|
||||
starsEarned: engine.starsEarned,
|
||||
objectiveProgress: engine.objectiveProgress,
|
||||
lastPlacement: lastPlacement,
|
||||
fxTick: _fxTick,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user