import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; import '../models/pokemon.dart'; /// GamePage - Main quiz game where users guess Pokémon from silhouettes class GamePage extends StatefulWidget { const GamePage({super.key}); @override State createState() => _GamePageState(); } class _GamePageState extends State { // Game state int score = 0; int lives = 3; Pokemon? currentPokemon; bool isShiny = false; bool isRevealed = false; bool showHint = false; bool isLoading = true; final TextEditingController _controller = TextEditingController(); final Random _random = Random(); @override void initState() { super.initState(); _loadNewPokemon(); } @override void dispose() { _controller.dispose(); super.dispose(); } /// Load a new random Pokémon (1-151) Future _loadNewPokemon() async { setState(() { isLoading = true; isRevealed = false; showHint = false; _controller.clear(); }); // Random ID between 1 and 151 final int randomId = _random.nextInt(151) + 1; // Shiny chance: 1/20 (5%) final bool shiny = _random.nextInt(20) == 0; // Fetch Pokémon using existing model (uses cache/API layer) final Pokemon? pokemon = await Pokemon.fromID(randomId); if (mounted) { setState(() { currentPokemon = pokemon; isShiny = shiny; isLoading = false; }); } } /// Validate user's guess void _validateGuess() { // Hide keyboard FocusScope.of(context).unfocus(); if (currentPokemon == null) return; final String userInput = _controller.text.trim().toLowerCase(); final String correctName = currentPokemon!.name.toLowerCase(); if (userInput == correctName) { _handleCorrectGuess(); } else { _handleWrongGuess(); } } /// Handle correct guess void _handleCorrectGuess() { final int points = isShiny ? 20 : 10; setState(() { isRevealed = true; score += points; }); // Show success message ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( isShiny ? '✨ SHINY! +$points points!' : '✅ Correct! +$points points!', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), backgroundColor: isShiny ? Colors.amber : Colors.green, duration: const Duration(seconds: 2), ), ); // Wait 2 seconds, then load new Pokémon Timer(const Duration(seconds: 2), () { if (mounted) { _loadNewPokemon(); } }); } /// Handle wrong guess void _handleWrongGuess() { setState(() { lives--; }); if (lives <= 0) { _showGameOverDialog(); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( '❌ Incorrect! Il reste $lives vie${lives > 1 ? 's' : ''}', style: const TextStyle(fontSize: 16), ), backgroundColor: Colors.redAccent, duration: const Duration(seconds: 1), ), ); } } /// Show game over dialog void _showGameOverDialog() { showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( backgroundColor: const Color(0xFF1A1A2E), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), title: const Text( 'GAME OVER', textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold, ), ), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'Le Pokémon était:', style: TextStyle(color: Colors.white70, fontSize: 16), ), const SizedBox(height: 8), Text( currentPokemon?.formatedName ?? '???', style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 24), const Text( 'Score final', style: TextStyle(color: Colors.white70, fontSize: 16), ), Text( '$score', style: const TextStyle( color: Color(0xFFE94560), fontSize: 48, fontWeight: FontWeight.bold, ), ), ], ), actions: [ Center( child: ElevatedButton( onPressed: () { Navigator.pop(context); _restartGame(); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFE94560), padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(25), ), ), child: const Text( 'REJOUER', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ], ), ); } /// Restart the game void _restartGame() { setState(() { score = 0; lives = 3; }); _loadNewPokemon(); } /// Use hint (costs 5 points) void _useHint() { if (score >= 5 && !showHint) { setState(() { score -= 5; showHint = true; }); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF1A1A2E), appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.pop(context), ), title: const Text( 'POKÉGUESS', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, letterSpacing: 2, ), ), centerTitle: true, ), body: SafeArea( child: Padding( padding: const EdgeInsets.all(20), child: Column( children: [ // Score and Lives row _buildScoreAndLives(), const SizedBox(height: 20), // Pokémon silhouette Expanded( child: _buildPokemonDisplay(), ), // Hint display if (showHint && currentPokemon != null) Padding( padding: const EdgeInsets.only(bottom: 16), child: Container( padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 10), decoration: BoxDecoration( color: currentPokemon!.type1Color.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(20), border: Border.all(color: currentPokemon!.type1Color), ), child: Text( 'Type: ${currentPokemon!.type1Formated}', style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ), // Input and buttons _buildInputSection(), ], ), ), ), ); } /// Build score and lives display Widget _buildScoreAndLives() { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Score Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(20), ), child: Row( children: [ const Icon(Icons.star, color: Colors.amber, size: 24), const SizedBox(width: 8), Text( '$score', style: const TextStyle( color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold, ), ), ], ), ), // Lives (hearts) Row( children: List.generate(3, (index) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Icon( index < lives ? Icons.favorite : Icons.favorite_border, color: const Color(0xFFE94560), size: 32, ), ); }), ), ], ); } /// Build Pokémon silhouette or revealed image Widget _buildPokemonDisplay() { if (isLoading) { return const Center( child: CircularProgressIndicator( color: Color(0xFFE94560), ), ); } if (currentPokemon == null) { return const Center( child: Text( 'Erreur de chargement', style: TextStyle(color: Colors.white), ), ); } final String imageUrl = isShiny ? currentPokemon!.shinyImageUrl : currentPokemon!.imageUrl; Widget image = Image.network( imageUrl, width: 250, height: 250, fit: BoxFit.contain, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return const SizedBox( width: 250, height: 250, child: Center( child: CircularProgressIndicator(color: Color(0xFFE94560)), ), ); }, errorBuilder: (context, error, stackTrace) { return const SizedBox( width: 250, height: 250, child: Center( child: Icon(Icons.error, color: Colors.red, size: 50), ), ); }, ); // Apply silhouette filter if not revealed if (!isRevealed) { image = ColorFiltered( colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn), child: image, ); } // Add shiny sparkle effect if revealed and shiny return Center( child: Stack( alignment: Alignment.center, children: [ // Glow effect when revealed if (isRevealed) Container( width: 280, height: 280, decoration: BoxDecoration( shape: BoxShape.circle, boxShadow: [ BoxShadow( color: isShiny ? Colors.amber.withValues(alpha: 0.4) : Colors.blueAccent.withValues(alpha: 0.3), blurRadius: 40, spreadRadius: 10, ), ], ), ), image, // Shiny indicator if (isShiny && isRevealed) const Positioned( top: 0, right: 50, child: Text( '✨', style: TextStyle(fontSize: 32), ), ), ], ), ); } /// Build input section with text field and buttons Widget _buildInputSection() { return Column( children: [ // Text input Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(15), ), child: TextField( controller: _controller, enabled: !isRevealed, style: const TextStyle(color: Colors.white, fontSize: 18), textAlign: TextAlign.center, textCapitalization: TextCapitalization.words, decoration: const InputDecoration( hintText: 'Nom du Pokémon...', hintStyle: TextStyle(color: Colors.white38), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 16), ), onSubmitted: (_) => _validateGuess(), ), ), const SizedBox(height: 16), // Buttons row Row( children: [ // Hint button Expanded( child: ElevatedButton.icon( onPressed: score >= 5 && !showHint && !isRevealed ? _useHint : null, icon: const Icon(Icons.lightbulb_outline, size: 20), label: const Text('Indice (5 pts)'), style: ElevatedButton.styleFrom( backgroundColor: Colors.amber.withValues(alpha: 0.8), foregroundColor: Colors.black87, disabledBackgroundColor: Colors.grey.withValues(alpha: 0.3), disabledForegroundColor: Colors.white38, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ), const SizedBox(width: 10), // Validate button Expanded( flex: 1, child: ElevatedButton( onPressed: !isRevealed ? _validateGuess : null, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFE94560), foregroundColor: Colors.white, disabledBackgroundColor: Colors.grey.withValues(alpha: 0.3), padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: const Text( 'VALIDER', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, letterSpacing: 1, ), ), ), ), ], ), ], ); } }