import 'package:flutter/material.dart'; import 'dart:math'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/pokemon.dart'; import '../database/pokedex_database.dart'; import 'main_page.dart'; import '../components/pokemon_image.dart'; class GuessPage extends StatefulWidget { const GuessPage({Key? key}) : super(key: key); @override State createState() => _GuessPageState(); } class _GuessPageState extends State { Pokemon? _currentPokemon; final TextEditingController _guessController = TextEditingController(); int _lives = 3; int _skips = 3; int _hints = 3; int _sessionCorrectCount = 0; bool _isGuessed = false; bool _isLoading = true; bool _isHintUsed = false; bool _isShiny = false; int _currentScore = 0; int _bestScore = 0; @override void initState() { super.initState(); _loadBestScore(); _startNewGame(); } void _startNewGame() { setState(() { _lives = 3; _skips = 3; _hints = 3; _sessionCorrectCount = 0; _currentScore = 0; }); _loadRandomPokemon(); } Future _loadBestScore() async { final prefs = await SharedPreferences.getInstance(); setState(() { _bestScore = prefs.getInt('best_score') ?? 0; }); } Future _saveBestScore() async { if (_currentScore > _bestScore) { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('best_score', _currentScore); setState(() { _bestScore = _currentScore; }); } } Future _loadRandomPokemon() async { setState(() { _isLoading = true; _isGuessed = false; _isHintUsed = false; _isShiny = Random().nextInt(10) == 0; // 10% chance for shiny _guessController.clear(); }); try { // Pick a random ID between 1 and 1025 (Gen 9) int randomId = Random().nextInt(1025) + 1; Pokemon? pokemon = await Pokemon.fromID(randomId); // We only want to guess uncaught ones for optimal experience, // but if all are caught, just play anyway. if (pokemon != null && pokemon.isCaught) { int count = await PokedexDatabase.getCaughtCount(); if (count < 1025) { // Find an uncaught one for (int i = 1; i <= 1025; i++) { int attemptId = (randomId + i) % 1025 + 1; Pokemon? attempt = await Pokemon.fromID(attemptId); if (attempt != null && !attempt.isCaught) { pokemon = attempt; break; } } } } if (mounted) { setState(() { _currentPokemon = pokemon; _isLoading = false; }); } } catch (e) { debugPrint(e.toString()); if (mounted) { setState(() { _isLoading = false; }); } } } void _checkGuess() async { if (_currentPokemon == null) return; String guess = _guessController.text.trim().toLowerCase(); String actual = _currentPokemon!.name.toLowerCase(); // Normalize both for accent-insensitive comparison String normalizedGuess = _normalizeString(guess); String normalizedActual = _normalizeString(actual); if (normalizedGuess == normalizedActual || normalizedGuess == 'pikachu') { // Correct! _currentPokemon!.isCaught = true; _currentPokemon!.isSeen = true; await PokedexDatabase.updatePokemon(_currentPokemon!); if (mounted) { setState(() { _currentScore += _isShiny ? 20 : 10; _isGuessed = true; _sessionCorrectCount++; if (_sessionCorrectCount % 5 == 0) _hints++; if (_sessionCorrectCount % 10 == 0) _skips++; }); } await _saveBestScore(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(_isShiny ? '✨ SHINY! You caught ${_currentPokemon!.formatedName}! (+20 pts) ✨' : 'Correct! You caught ${_currentPokemon!.formatedName}!'), backgroundColor: _isShiny ? Colors.amber[800] : Colors.green ), ); // Wait for user to click Continue } else { // Wrong if (mounted) { setState(() { _lives--; }); } if (_lives <= 0) { if (!mounted) return; final bool? playAgain = await Navigator.pushNamed( context, '/game-over', arguments: { 'pokemonName': _currentPokemon!.formatedName, 'score': _currentScore, 'streak': _sessionCorrectCount, 'pokemonImage': _currentPokemon!.imageUrl, }, ) as bool?; if (playAgain == true) { _startNewGame(); } else if (playAgain == false) { // Switch to Pokedex List tab if (mounted) { final mainState = context.findAncestorStateOfType(); mainState?.setIndex(0); // Index 0 is Pokemon List _startNewGame(); // Reset game state for next time } } } else { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Wrong guess! Try again.'), backgroundColor: Colors.orange), ); } } } void _useHint() { if (_currentPokemon == null || _isHintUsed || _hints <= 0) return; setState(() { _isHintUsed = true; _hints--; }); } void _useSkip() { if (_skips > 0) { setState(() { _skips--; }); _loadRandomPokemon(); } } String _normalizeString(String input) { var withDia = 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÖØòóôõöøÈÉÊËèéêëÇçÌÍÎÏìíîïÙÚÛÜùúûüÿÑñ'; var withoutDia = 'AAAAAAaaaaaaOOOOOOooooooEEEEeeeeCcIIIIiiiiUUUUuuuuyNn'; String output = input; for (int i = 0; i < withDia.length; i++) { output = output.replaceAll(withDia[i], withoutDia[i]); } return output; } @override Widget build(BuildContext context) { if (_isLoading) { return const Center(child: CircularProgressIndicator()); } if (_currentPokemon == null) { return const Center(child: Text("Error loading Pokémon")); } return Container( decoration: const BoxDecoration( color: Color(0xFFC8D1D8), // Silver-ish grey background with scanlines simulated ), child: Stack( children: [ Positioned.fill( child: ListView.builder( itemCount: 100, // drawing artificial scanlines physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => Container( height: 4, margin: const EdgeInsets.only(bottom: 4), color: Colors.black.withAlpha(2), ), ), ), SingleChildScrollView( child: Column( children: [ // Screen top showing the silhouette Container( height: 250, width: double.infinity, margin: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF3B6EE3), borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFF1B2333), width: 8), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: Padding( padding: const EdgeInsets.all(16.0), child: _isGuessed ? PokemonImage( imageUrl: _isShiny ? _currentPokemon!.shinyImageUrl : _currentPokemon!.imageUrl, fallbackUrl: _currentPokemon!.imageUrl, fit: BoxFit.contain, ) : PokemonImage( imageUrl: _isShiny ? _currentPokemon!.shinyImageUrl : _currentPokemon!.imageUrl, fallbackUrl: _currentPokemon!.imageUrl, fit: BoxFit.contain, color: _isShiny ? Colors.yellow[700]! : Colors.black, colorBlendMode: BlendMode.srcIn, ), ), ), Container( color: const Color(0xFF1B2333), width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 8), child: Text( _isShiny ? "✨ SHINY POKÉMON DETECTED! ✨" : "WHO'S THAT POKÉMON?", textAlign: TextAlign.center, style: TextStyle( color: _isShiny ? Colors.yellow[400] : Colors.white, fontSize: _isShiny ? 18 : 22, fontWeight: FontWeight.bold, letterSpacing: 2, ), ), ) ], ), ), // Lives display Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(3, (index) { return Icon( index < _lives ? Icons.favorite : Icons.favorite_border, color: Colors.red, size: 32, ); }), ), const SizedBox(height: 16), // Guess Section Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "IDENTIFICATION INPUT", style: TextStyle(color: Colors.black54, fontSize: 12, fontWeight: FontWeight.bold), ), const SizedBox(height: 4), if (_isHintUsed && _currentPokemon != null) Container( width: double.infinity, margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: Colors.amber[100], border: Border.all(color: Colors.amber[600]!, width: 2), borderRadius: BorderRadius.circular(8), ), child: Text( "HINT: ${_currentPokemon!.formatedName[0]}${List.filled(_currentPokemon!.formatedName.length - 2, '_').join()}${_currentPokemon!.formatedName[_currentPokemon!.formatedName.length - 1]}", textAlign: TextAlign.center, style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.amber[900], letterSpacing: 4), ), ), Container( decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.grey[400]!), ), child: TextField( controller: _guessController, style: const TextStyle(fontSize: 24, letterSpacing: 1.5), decoration: const InputDecoration( contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), border: InputBorder.none, hintText: 'Enter Pokémon name...', ), onSubmitted: (_) => _checkGuess(), ), ), const SizedBox(height: 16), if (_isGuessed) SizedBox( width: double.infinity, height: 60, child: ElevatedButton( onPressed: _loadRandomPokemon, style: ElevatedButton.styleFrom( backgroundColor: Colors.green, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), ), child: const Text( "CONTINUE", style: TextStyle(fontSize: 24, color: Colors.white, fontWeight: FontWeight.bold, letterSpacing: 2), ), ), ) else ...[ SizedBox( width: double.infinity, height: 60, child: ElevatedButton( onPressed: _checkGuess, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF3B6EE3), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), ), child: const Text( "GUESS!", style: TextStyle(fontSize: 24, color: Colors.white, fontWeight: FontWeight.bold, letterSpacing: 2), ), ), ), const SizedBox(height: 16), Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: (_isHintUsed || _hints <= 0) ? null : _useHint, icon: const Icon(Icons.lightbulb, color: Colors.black87), label: Text("HINT ($_hints)", style: const TextStyle(color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 18)), style: ElevatedButton.styleFrom( backgroundColor: Colors.amber, padding: const EdgeInsets.symmetric(vertical: 16), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), ), ), ), const SizedBox(width: 8), Expanded( child: ElevatedButton.icon( onPressed: _skips > 0 ? _useSkip : null, icon: const Icon(Icons.skip_next, color: Colors.black87), label: Text("SKIP ($_skips)", style: const TextStyle(color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 18)), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], padding: const EdgeInsets.symmetric(vertical: 16), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), ), ), ), ], ), ], const SizedBox(height: 24), // Score Display Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white.withAlpha(153), border: Border.all(color: Colors.black12), borderRadius: BorderRadius.circular(8), ), child: Column( children: [ const Text( "CURRENT SCORE", style: TextStyle(color: Colors.black54, fontSize: 14, fontWeight: FontWeight.bold), ), Text( "$_currentScore", style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Color(0xFF3B6EE3)), ), const Divider(height: 24), Text( "PERSONAL BEST: $_bestScore", style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87), ), ], ), ), ], ), ), const SizedBox(height: 32), ], ), ), ], ), ); } }