diff --git a/lib/database/pokedex_database.dart b/lib/database/pokedex_database.dart index fd5064f..68cc818 100644 --- a/lib/database/pokedex_database.dart +++ b/lib/database/pokedex_database.dart @@ -101,4 +101,12 @@ class PokedexDatabase { int count = result.isNotEmpty ? (result.first.values.first as int? ?? 0) : 0; return count; } + + // Obtenir le nombre de pokémon vus + static Future getSeenCount() async { + Database database = await getDatabase(); + var result = await database.rawQuery("SELECT COUNT(*) FROM pokemon WHERE isSeen = 1"); + int count = result.isNotEmpty ? (result.first.values.first as int? ?? 0) : 0; + return count; + } } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index a7aec35..215c0d4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'pages/pokemon_detail.dart'; import 'pages/main_page.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import 'pages/game_over_page.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -39,6 +40,7 @@ class MyApp extends StatelessWidget { routes: { '/': (context) => const MainPage(), // La route "/" est la page d'accueil avec BottomNav '/pokemon-detail':(context) => const PokemonDetailPage(), + '/game-over': (context) => const GameOverPage(), } ); } diff --git a/lib/pages/game_over_page.dart b/lib/pages/game_over_page.dart new file mode 100644 index 0000000..f205685 --- /dev/null +++ b/lib/pages/game_over_page.dart @@ -0,0 +1,302 @@ +import 'package:flutter/material.dart'; +import '../database/pokedex_database.dart'; + +class GameOverPage extends StatefulWidget { + const GameOverPage({Key? key}) : super(key: key); + + @override + State createState() => _GameOverPageState(); +} + +class _GameOverPageState extends State { + int _seenCount = 0; + bool _isLoading = true; + + @override + void initState() { + super.initState(); + _loadSeenCount(); + } + + Future _loadSeenCount() async { + int count = await PokedexDatabase.getSeenCount(); + if (mounted) { + setState(() { + _seenCount = count; + _isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + final Map? args = + ModalRoute.of(context)!.settings.arguments as Map?; + + final String pokemonImage = args?['pokemonImage'] ?? ''; + final String pokemonName = args?['pokemonName'] ?? 'Unknown'; + final int streak = args?['streak'] ?? 0; + + // Pad streak with zeroes to 3 digits as in mockup (e.g. 004) + final String streakText = streak.toString().padLeft(3, '0'); + + // Define color palette from mockup + const Color pokedexRed = Color(0xFFD32F2F); + const Color darkRed = Color(0xFF9E1B1B); + const Color silverBg = Color(0xFFC8D1D8); + const Color messageBoxBg = Color(0xFF1B2333); + const Color statBoxBg = Color(0xFFD9E0E5); // slightly lighter/different silver for stats + const Color tryAgainBtn = Color(0xFF2962FF); // Blue + const Color backBtn = Color(0xFFA66A00); // Brown + + return Scaffold( + backgroundColor: pokedexRed, + body: _isLoading + ? const Center(child: CircularProgressIndicator(color: Colors.white)) + : SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + // Top Box: Pokemon Silhouette & GAME OVER + Container( + decoration: BoxDecoration( + color: darkRed, // Border color + border: Border.all(color: darkRed, width: 4), + ), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 16), + color: silverBg, + child: Column( + children: [ + // GAME OVER Banner + Container( + color: darkRed, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + child: const Text( + "GAME OVER", + style: TextStyle( + fontSize: 26, + color: Colors.yellow, + fontWeight: FontWeight.bold, + letterSpacing: 2, + shadows: [ + Shadow( + offset: Offset(1.5, 1.5), + color: Colors.black, + ), + ], + ), + ), + ), + const SizedBox(height: 16), + // Pokemon Image and Name + if (pokemonImage.isNotEmpty) + SizedBox( + height: 140, + child: Image.network(pokemonImage, fit: BoxFit.contain), + ), + const SizedBox(height: 12), + Text( + "It was $pokemonName!", + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Color(0xFF1B2333), + letterSpacing: 1, + ), + ), + const SizedBox(height: 8), + ], + ), + ), + ), + + // Divider between boxes + Padding( + padding: const EdgeInsets.symmetric(vertical: 24.0), + child: Row( + children: [ + Expanded( + child: Container(height: 2, color: darkRed), + ), + const SizedBox(width: 8), + Row( + mainAxisSize: MainAxisSize.min, + children: List.generate(3, (index) => + Container( + width: 8, + height: 8, + margin: const EdgeInsets.symmetric(horizontal: 4), + decoration: const BoxDecoration( + color: darkRed, + shape: BoxShape.circle, + ), + ) + ), + ), + const SizedBox(width: 8), + Expanded( + child: Container(height: 2, color: darkRed), + ), + ], + ), + ), + + // Bottom Box: Message, Stats, Buttons + Container( + decoration: BoxDecoration( + color: darkRed, + border: Border.all(color: darkRed, width: 4), + ), + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + color: silverBg, + child: Column( + children: [ + // Message Box + Container( + width: double.infinity, + color: messageBoxBg, + padding: const EdgeInsets.all(24), + child: const Text( + "\"Looks like your journey\nends here. You've run out\nof energy!\"", + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.normal, + height: 1.5, + ), + ), + ), + const SizedBox(height: 16), + + // Stats Row + Row( + children: [ + Expanded( + child: Container( + color: statBoxBg, + padding: const EdgeInsets.symmetric(vertical: 12), + child: Column( + children: [ + const Text( + "STREAK", + style: TextStyle( + color: Colors.red, + fontSize: 10, + fontWeight: FontWeight.bold, + letterSpacing: 1, + ), + ), + const SizedBox(height: 4), + Text( + streakText, + style: const TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Container( + color: statBoxBg, + padding: const EdgeInsets.symmetric(vertical: 12), + child: Column( + children: [ + const Text( + "SEEN", + style: TextStyle( + color: Colors.red, + fontSize: 10, + fontWeight: FontWeight.bold, + letterSpacing: 1, + ), + ), + const SizedBox(height: 4), + Text( + "$_seenCount/1025", // Gen 9 total + style: const TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 16), + + // Try Again Button + SizedBox( + width: double.infinity, + height: 60, + child: ElevatedButton.icon( + onPressed: () { + Navigator.pop(context, true); + }, + icon: const Icon(Icons.refresh, color: Colors.white, size: 24), + label: const Text( + "TRY AGAIN", + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + letterSpacing: 2, + ), + ), + style: ElevatedButton.styleFrom( + backgroundColor: tryAgainBtn, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + ), + ), + ), + const SizedBox(height: 16), + + // Back to Pokedex Button + SizedBox( + width: double.infinity, + height: 60, + child: ElevatedButton.icon( + onPressed: () { + Navigator.pop(context, false); + }, + icon: const Icon(Icons.menu_book, color: Colors.white, size: 24), + label: const Text( + "BACK TO POKEDEX", + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + letterSpacing: 2, + ), + ), + style: ElevatedButton.styleFrom( + backgroundColor: backBtn, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/guess_page.dart b/lib/pages/guess_page.dart index 7258d4b..8e19049 100644 --- a/lib/pages/guess_page.dart +++ b/lib/pages/guess_page.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/pokemon.dart'; import '../database/pokedex_database.dart'; +import 'main_page.dart'; class GuessPage extends StatefulWidget { const GuessPage({Key? key}) : super(key: key); @@ -15,6 +16,10 @@ 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; @@ -25,6 +30,17 @@ class _GuessPageState extends State { void initState() { super.initState(); _loadBestScore(); + _startNewGame(); + } + + void _startNewGame() { + setState(() { + _lives = 3; + _skips = 3; + _hints = 3; + _sessionCorrectCount = 0; + _currentScore = 0; + }); _loadRandomPokemon(); } @@ -48,7 +64,7 @@ class _GuessPageState extends State { Future _loadRandomPokemon() async { setState(() { _isLoading = true; - _lives = 3; + _isGuessed = false; _isHintUsed = false; _isShiny = Random().nextInt(10) == 0; // 10% chance for shiny _guessController.clear(); @@ -110,6 +126,10 @@ class _GuessPageState extends State { if (mounted) { setState(() { _currentScore += _isShiny ? 20 : 10; + _isGuessed = true; + _sessionCorrectCount++; + if (_sessionCorrectCount % 5 == 0) _hints++; + if (_sessionCorrectCount % 10 == 0) _skips++; }); } await _saveBestScore(); @@ -123,9 +143,7 @@ class _GuessPageState extends State { backgroundColor: _isShiny ? Colors.amber[800] : Colors.green ), ); - - // Load next - _loadRandomPokemon(); + // Wait for user to click Continue } else { // Wrong if (mounted) { @@ -135,17 +153,28 @@ class _GuessPageState extends State { } if (_lives <= 0) { - if (mounted) { - setState(() { - _currentScore = 0; // Reset score only when all lives are lost - }); - } if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Out of lives! It was ${_currentPokemon!.formatedName}.'), backgroundColor: Colors.red), - ); - // Load next - _loadRandomPokemon(); + 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( @@ -156,17 +185,20 @@ class _GuessPageState extends State { } void _useHint() { - if (_currentPokemon == null || _isHintUsed) return; + if (_currentPokemon == null || _isHintUsed || _hints <= 0) return; setState(() { _isHintUsed = true; - // Provide a hint like replacing some characters with underscores, or telling type - // For simplicity, we put the first letter and last letter + _hints--; }); - String name = _currentPokemon!.formatedName; - String hint = '${name[0]}${List.filled(name.length - 2, '_').join()}${name[name.length - 1]}'; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Hint: $hint'), duration: const Duration(seconds: 4)), - ); + } + + void _useSkip() { + if (_skips > 0) { + setState(() { + _skips--; + }); + _loadRandomPokemon(); + } } String _normalizeString(String input) { @@ -225,13 +257,15 @@ class _GuessPageState extends State { Expanded( child: Padding( padding: const EdgeInsets.all(16.0), - child: ColorFiltered( - colorFilter: ColorFilter.mode( - _isShiny ? Colors.yellow[700]! : Colors.black, - BlendMode.srcIn - ), - child: Image.network(_currentPokemon!.imageUrl, fit: BoxFit.contain), - ), + child: _isGuessed + ? Image.network(_currentPokemon!.imageUrl, fit: BoxFit.contain) + : ColorFiltered( + colorFilter: ColorFilter.mode( + _isShiny ? Colors.yellow[700]! : Colors.black, + BlendMode.srcIn + ), + child: Image.network(_currentPokemon!.imageUrl, fit: BoxFit.contain), + ), ), ), Container( @@ -277,6 +311,22 @@ class _GuessPageState extends State { 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, @@ -294,51 +344,69 @@ class _GuessPageState extends State { ), ), const SizedBox(height: 16), - SizedBox( - width: double.infinity, - height: 60, - child: ElevatedButton( - onPressed: _checkGuess, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF3B6EE3), - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + 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), + ), ), - child: const Text( - "GUESS!", - 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 ? null : _useHint, - icon: const Icon(Icons.lightbulb, color: Colors.black87), - label: const Text("HINT", style: 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(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: _loadRandomPokemon, - icon: const Icon(Icons.skip_next, color: Colors.black87), - label: const Text("SKIP", style: 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(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 diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart index 2465e7f..6b27fe5 100644 --- a/lib/pages/main_page.dart +++ b/lib/pages/main_page.dart @@ -6,12 +6,18 @@ class MainPage extends StatefulWidget { const MainPage({Key? key}) : super(key: key); @override - State createState() => _MainPageState(); + State createState() => MainPageState(); } -class _MainPageState extends State { +class MainPageState extends State { int _currentIndex = 0; + void setIndex(int index) { + setState(() { + _currentIndex = index; + }); + } + final List _pages = [ const PokemonListPage(), const GuessPage(), diff --git a/lib/pages/quel-est-ce-pokemon.code-workspace b/lib/pages/quel-est-ce-pokemon.code-workspace new file mode 100644 index 0000000..407c760 --- /dev/null +++ b/lib/pages/quel-est-ce-pokemon.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "../.." + } + ], + "settings": {} +} \ No newline at end of file