El Impostor v0.1 — app Flutter completa

Juego de deducción social para 3-20 jugadores.
Modo un solo móvil completamente funcional.
1000 palabras en 10 categorías.
Notas privadas, votación, adivinanza, revancha.
Material 3 dark theme.
Package: es.freetimelab.elimpostor
This commit is contained in:
ShanaiaBot
2026-04-04 00:50:04 +02:00
parent eb7661cb36
commit de2c8ffa18
45 changed files with 4206 additions and 0 deletions

View File

@@ -0,0 +1,236 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../estado/estado_juego.dart';
import '../tema/tema_app.dart';
import 'pantalla_debate.dart';
import 'pantalla_fin_partida.dart';
class PantallaAdivinanza extends StatefulWidget {
const PantallaAdivinanza({super.key});
@override
State<PantallaAdivinanza> createState() => _PantallaAdivinanzaState();
}
class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
final _controlador = TextEditingController();
bool? _acierto;
@override
void dispose() {
_controlador.dispose();
super.dispose();
}
void _intentarAdivinar() {
final estado = context.read<EstadoJuego>();
final resultado = estado.intentarAdivinar(_controlador.text);
setState(() => _acierto = resultado);
}
@override
Widget build(BuildContext context) {
final estado = context.watch<EstadoJuego>();
final partida = estado.partida;
if (partida == null) return const SizedBox.shrink();
return Scaffold(
appBar: AppBar(
title: const Text('🎯 Adivinanza del impostor'),
automaticallyImplyLeading: false,
),
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('🎭', style: TextStyle(fontSize: 64)),
const SizedBox(height: 16),
Text(
'El impostor eliminado puede\nintentar adivinar la palabra',
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Si acierta, ¡los impostores ganan!',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: TemaApp.colorNaranja,
),
),
const SizedBox(height: 32),
if (_acierto == null) ...[
TextField(
controller: _controlador,
decoration: const InputDecoration(
hintText: '¿Cuál crees que es la palabra?',
prefixIcon: Icon(Icons.search),
),
textCapitalization: TextCapitalization.sentences,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20),
onSubmitted: (_) => _intentarAdivinar(),
),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {
// No intenta adivinar, siguiente ronda
final fin = estado.comprobarFinPartida();
if (fin) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const PantallaFinPartida(),
),
);
} else {
estado.siguienteRonda();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const PantallaDebate(),
),
);
}
},
child: const Text('No intentar'),
),
),
const SizedBox(width: 12),
Expanded(
flex: 2,
child: ElevatedButton.icon(
onPressed: _controlador.text.trim().isNotEmpty
? _intentarAdivinar
: null,
icon: const Icon(Icons.send),
label: const Text('Adivinar'),
),
),
],
),
],
if (_acierto == true) ...[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: TemaApp.colorAcento.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: TemaApp.colorAcento),
),
child: Column(
children: [
const Text('🎭🎉', style: TextStyle(fontSize: 48)),
const SizedBox(height: 12),
Text(
'¡Ha acertado!',
style: Theme.of(context)
.textTheme
.headlineMedium
?.copyWith(color: TemaApp.colorAcento),
),
const SizedBox(height: 8),
Text(
'La palabra era: ${partida.palabraSecreta}',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
'¡Los impostores ganan!',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: TemaApp.colorNaranja,
),
),
],
),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton.icon(
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const PantallaFinPartida(),
),
);
},
icon: const Icon(Icons.emoji_events),
label: const Text('Ver resultado final'),
),
),
],
if (_acierto == false) ...[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: TemaApp.colorVerde.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: TemaApp.colorVerde),
),
child: Column(
children: [
const Text('', style: TextStyle(fontSize: 48)),
const SizedBox(height: 12),
Text(
'¡No ha acertado!',
style: Theme.of(context)
.textTheme
.headlineMedium
?.copyWith(color: TemaApp.colorVerde),
),
const SizedBox(height: 8),
Text(
'La partida continúa...',
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton.icon(
onPressed: () {
final fin = estado.comprobarFinPartida();
if (fin) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const PantallaFinPartida(),
),
);
} else {
estado.siguienteRonda();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const PantallaDebate(),
),
);
}
},
icon: const Icon(Icons.skip_next),
label: const Text('Siguiente ronda'),
),
),
],
],
),
),
),
);
}
}