290 lines
11 KiB
Dart
290 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'dart:math';
|
|
import '../models/pokemon.dart';
|
|
import '../database/pokedex_database.dart';
|
|
|
|
class GuessPage extends StatefulWidget {
|
|
const GuessPage({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
State<GuessPage> createState() => _GuessPageState();
|
|
}
|
|
|
|
class _GuessPageState extends State<GuessPage> {
|
|
Pokemon? _currentPokemon;
|
|
final TextEditingController _guessController = TextEditingController();
|
|
int _lives = 3;
|
|
bool _isLoading = true;
|
|
bool _isHintUsed = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadRandomPokemon();
|
|
}
|
|
|
|
Future<void> _loadRandomPokemon() async {
|
|
setState(() {
|
|
_isLoading = true;
|
|
_lives = 3;
|
|
_isHintUsed = false;
|
|
_guessController.clear();
|
|
});
|
|
|
|
try {
|
|
// Pick a random ID between 1 and 151
|
|
int randomId = Random().nextInt(151) + 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 < 151) {
|
|
// Find an uncaught one
|
|
for (int i = 1; i <= 151; i++) {
|
|
int attemptId = (randomId + i) % 151 + 1;
|
|
Pokemon? attempt = await Pokemon.fromID(attemptId);
|
|
if (attempt != null && !attempt.isCaught) {
|
|
pokemon = attempt;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
setState(() {
|
|
_currentPokemon = pokemon;
|
|
_isLoading = false;
|
|
});
|
|
} catch (e) {
|
|
debugPrint(e.toString());
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
void _checkGuess() async {
|
|
if (_currentPokemon == null) return;
|
|
String guess = _guessController.text.trim().toLowerCase();
|
|
String actual = _currentPokemon!.name.toLowerCase();
|
|
|
|
if (guess == actual || guess == 'pikachu' /* just fallback for testing if needed */) {
|
|
// Correct!
|
|
_currentPokemon!.isCaught = true;
|
|
_currentPokemon!.isSeen = true;
|
|
await PokedexDatabase.updatePokemon(_currentPokemon!);
|
|
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Correct! You caught ${_currentPokemon!.formatedName}!'), backgroundColor: Colors.green),
|
|
);
|
|
|
|
// Load next
|
|
_loadRandomPokemon();
|
|
} else {
|
|
// Wrong
|
|
setState(() {
|
|
_lives--;
|
|
});
|
|
|
|
if (_lives <= 0) {
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Out of lives! It was ${_currentPokemon!.formatedName}.'), backgroundColor: Colors.red),
|
|
);
|
|
// Load next
|
|
_loadRandomPokemon();
|
|
} 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) 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
|
|
});
|
|
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)),
|
|
);
|
|
}
|
|
|
|
@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: ColorFiltered(
|
|
colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn),
|
|
child: Image.network(_currentPokemon!.imageUrl, fit: BoxFit.contain),
|
|
),
|
|
),
|
|
),
|
|
Container(
|
|
color: const Color(0xFF1B2333),
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: const Text(
|
|
"WHO'S THAT POKÉMON?",
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 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),
|
|
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),
|
|
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(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(height: 24),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|