import 'package:flame/game.dart'; import 'package:flutter/material.dart'; import '../components/result_modal.dart'; import '../flame/memory_match_flame_game.dart'; import '../game/levels.dart'; class GameScreenFlame extends StatefulWidget { const GameScreenFlame({super.key, this.levelId = 1}); final int levelId; @override State createState() => _GameScreenFlameState(); } class _GameScreenFlameState extends State { late int currentLevelId; late MemoryMatchFlameGame flameGame; @override void initState() { super.initState(); currentLevelId = widget.levelId; flameGame = _buildGame(currentLevelId); } MemoryMatchFlameGame _buildGame(int levelId) { final level = levels[(levelId - 1).clamp(0, levels.length - 1)]; return MemoryMatchFlameGame( level: level, onStateChanged: () { if (mounted) { setState(() {}); } }, onResult: (status, score) { if (mounted) { setState(() {}); } }, ); } Future _restartSameLevel() async { await flameGame.restartSameLevel(); if (mounted) { setState(() {}); } } Future _goNext() async { if (currentLevelId >= levels.length) { await _restartSameLevel(); return; } currentLevelId += 1; await flameGame.nextLevel(levels[currentLevelId - 1]); if (mounted) { setState(() {}); } } @override Widget build(BuildContext context) { final isReady = flameGame.isReady; final scoreText = isReady ? '${flameGame.controller.score}' : '0'; return Scaffold( body: Stack( children: [ GameWidget(game: flameGame), SafeArea( child: Column( children: [ Padding( padding: EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width >= 768 ? 22 : 12, vertical: 8, ), child: _TopHud( flameGame: flameGame, onPause: () { flameGame.togglePause(); }, onMusic: () { flameGame.toggleMusic(); }, onSfx: () { flameGame.toggleSfx(); }, ), ), Text( 'Level ${flameGame.level.id}', style: const TextStyle( fontSize: 34, fontWeight: FontWeight.w900, color: Color(0xFF56F0FF), letterSpacing: 1, shadows: [ Shadow( color: Color.fromRGBO(80, 255, 255, 0.55), blurRadius: 12, offset: Offset(0, 2), ), ], ), ), const Spacer(), Padding( padding: EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width >= 768 ? 22 : 18, ), child: _BottomHud( flameGame: flameGame, scoreText: scoreText, onRetry: () { _restartSameLevel(); }, ), ), // Padding( // padding: const EdgeInsets.symmetric(vertical: 6), // child: Text( // 'Moves: $moves Mistakes: $mistakes${flameGame.level.maxMistakes != null ? ' / ${flameGame.level.maxMistakes}' : ''} Grid: ${flameGame.level.rowPattern.join('-')}', // style: const TextStyle( // color: Color.fromRGBO(255, 255, 255, 0.85), // ), // ), // ), ], ), ), if (flameGame.pausedByUser) Positioned.fill( child: _PauseOverlay( onResume: () { flameGame.togglePause(); }, onRetry: () { _restartSameLevel(); }, onHome: () { Navigator.of(context).pushReplacementNamed('/start'); }, ), ), ResultModal( visible: flameGame.showResult, status: flameGame.resultStatus, score: flameGame.finalScore, bestScore: flameGame.bestScore, hasNext: currentLevelId + 1 <= levels.length, onRetry: _restartSameLevel, onNext: _goNext, showAddTime: flameGame.resultStatus == 'lost', addTimeSeconds: 30, onRequestRewardAddTime: (grant) async { await Future.delayed(const Duration(milliseconds: 600)); final sec = await flameGame.reviveWithReward(); grant(sec); }, ), ], ), ); } } class _TopHud extends StatelessWidget { const _TopHud({ required this.flameGame, required this.onPause, required this.onMusic, required this.onSfx, }); final MemoryMatchFlameGame flameGame; final VoidCallback onPause; final VoidCallback onMusic; final VoidCallback onSfx; @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final compact = constraints.maxWidth < 380; final iconSize = compact ? 42.0 : 46.0; final itemGap = compact ? 6.0 : 10.0; final groupGap = compact ? 8.0 : 12.0; final topBadgeHeight = compact ? 52.0 : 58.0; final rightGroupWidth = iconSize * 2 + itemGap; final timerWidth = (constraints.maxWidth - iconSize - rightGroupWidth - groupGap * 2) .clamp(118.0, 176.0); return Row( children: [ _NeonIconButton( iconAsset: flameGame.pausedByUser ? 'assets/images/0f3f8a4a21cdb16db2f080e4d7695cdc.png' : 'assets/images/e86bdd5d8908079d1ed4c06015f37eb0.png', onTap: onPause, size: iconSize, ), SizedBox(width: groupGap), _HudValueBadge( backgroundAsset: 'assets/images/b5bf27b2555de44e3df2230080db5a1d.png', text: flameGame.timeText, width: timerWidth, height: topBadgeHeight, textLeftInset: compact ? 64 : 76, textRightInset: compact ? 12 : 16, textStyle: TextStyle( color: const Color(0xFF69F6FF), fontSize: compact ? 16 : 18, fontWeight: FontWeight.w900, letterSpacing: compact ? 0.5 : 1, ), ), const Spacer(), _NeonIconButton( iconAsset: flameGame.mutedMusic ? 'assets/images/5efe427a4e20b0ab12afe98c3cb50a60.png' : 'assets/images/5efe427a4e20b0ab12afe98c3cb50a60.png', active: !flameGame.mutedMusic, onTap: onMusic, size: iconSize, ), SizedBox(width: itemGap), _NeonIconButton( iconAsset: flameGame.mutedSfx ? 'assets/images/849ade26e1b6da2ed96d00db639a0985.png' : 'assets/images/1548af1c94ad45584324df8f08baf227.png', active: !flameGame.mutedSfx, onTap: onSfx, size: iconSize, ), ], ); }, ); } } class _BottomHud extends StatelessWidget { const _BottomHud({ required this.flameGame, required this.scoreText, required this.onRetry, }); final MemoryMatchFlameGame flameGame; final String scoreText; final VoidCallback onRetry; @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _HudValueBadge( backgroundAsset: 'assets/images/5e36941b3d856737e81516acd45edc50.png', text: scoreText, width: 118, height: 44, textLeftInset: 54, textRightInset: 10, textStyle: const TextStyle( color: Color(0xFF56F0FF), fontSize: 18, fontWeight: FontWeight.w900, letterSpacing: 1, ), ), _NeonIconButton( iconAsset: 'assets/images/161747ec4dc9f55f1760195593742232.png', onTap: onRetry, ), ], ); } } class _HudValueBadge extends StatelessWidget { const _HudValueBadge({ required this.backgroundAsset, required this.text, required this.width, required this.height, required this.textLeftInset, required this.textRightInset, required this.textStyle, }); final String backgroundAsset; final String text; final double width; final double height; final double textLeftInset; final double textRightInset; final TextStyle textStyle; @override Widget build(BuildContext context) { return SizedBox( width: width, height: height, child: Stack( children: [ Positioned.fill( child: Image.asset(backgroundAsset, fit: BoxFit.fill), ), Positioned.fill( left: textLeftInset, right: textRightInset, child: Align( alignment: Alignment.center, child: FittedBox( fit: BoxFit.scaleDown, child: Text(text, maxLines: 1, style: textStyle), ), ), ), ], ), ); } } class _NeonIconButton extends StatelessWidget { const _NeonIconButton({ required this.iconAsset, required this.onTap, this.active = true, this.size = 46, }); final String iconAsset; final VoidCallback onTap; final bool active; final double size; @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: SizedBox( width: size, height: size, child: Center( child: Opacity( opacity: active ? 1 : 0.45, child: Image.asset(iconAsset, width: size, height: size), ), ), ), ); } } class _PauseOverlay extends StatelessWidget { const _PauseOverlay({ required this.onResume, required this.onRetry, required this.onHome, }); final VoidCallback onResume; final VoidCallback onRetry; final VoidCallback onHome; @override Widget build(BuildContext context) { return Material( color: const Color.fromRGBO(0, 0, 0, 0.65), child: Center( child: Container( width: MediaQuery.of(context).size.width.clamp(0, 420).toDouble(), margin: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color.fromRGBO(20, 24, 60, 0.96), borderRadius: BorderRadius.circular(18), border: Border.all( color: const Color.fromRGBO(120, 255, 255, 0.25), width: 2, ), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'Paused', style: TextStyle( fontSize: 20, fontWeight: FontWeight.w900, color: Colors.white, letterSpacing: 1, ), ), const SizedBox(height: 12), _PauseButton( text: 'Resume', color: const Color.fromRGBO(50, 160, 255, 0.9), onTap: onResume, ), const SizedBox(height: 10), _PauseButton( text: 'Retry', color: const Color.fromRGBO(255, 255, 255, 0.1), onTap: onRetry, ), const SizedBox(height: 10), _PauseButton( text: 'Home', color: const Color.fromRGBO(255, 120, 120, 0.22), onTap: onHome, ), ], ), ), ), ); } } class _PauseButton extends StatelessWidget { const _PauseButton({ required this.text, required this.color, required this.onTap, }); final String text; final Color color; final VoidCallback onTap; @override Widget build(BuildContext context) { return SizedBox( width: double.infinity, height: 48, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: color, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), side: const BorderSide( color: Color.fromRGBO(255, 255, 255, 0.22), width: 2, ), ), onPressed: onTap, child: Text( text, style: const TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.w900, letterSpacing: 0.5, ), ), ), ); } }