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:
51
lib/modelos/jugador.dart
Normal file
51
lib/modelos/jugador.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
/// Representa un jugador en la partida
|
||||
class Jugador {
|
||||
final String id;
|
||||
final String nombre;
|
||||
bool esImpostor;
|
||||
bool eliminado;
|
||||
String? palabra;
|
||||
String? endpointId; // Para modo multimóvil
|
||||
Map<String, String> notas; // nombre_jugador -> nota
|
||||
|
||||
Jugador({
|
||||
required this.id,
|
||||
required this.nombre,
|
||||
this.esImpostor = false,
|
||||
this.eliminado = false,
|
||||
this.palabra,
|
||||
this.endpointId,
|
||||
Map<String, String>? notas,
|
||||
}) : notas = notas ?? {};
|
||||
|
||||
Jugador copiar({
|
||||
bool? esImpostor,
|
||||
bool? eliminado,
|
||||
String? palabra,
|
||||
String? endpointId,
|
||||
}) {
|
||||
return Jugador(
|
||||
id: id,
|
||||
nombre: nombre,
|
||||
esImpostor: esImpostor ?? this.esImpostor,
|
||||
eliminado: eliminado ?? this.eliminado,
|
||||
palabra: palabra ?? this.palabra,
|
||||
endpointId: endpointId ?? this.endpointId,
|
||||
notas: Map.from(notas),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'nombre': nombre,
|
||||
'esImpostor': esImpostor,
|
||||
'eliminado': eliminado,
|
||||
};
|
||||
|
||||
factory Jugador.fromJson(Map<String, dynamic> json) => Jugador(
|
||||
id: json['id'] as String,
|
||||
nombre: json['nombre'] as String,
|
||||
esImpostor: json['esImpostor'] as bool? ?? false,
|
||||
eliminado: json['eliminado'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
63
lib/modelos/palabra.dart
Normal file
63
lib/modelos/palabra.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Categorías disponibles en el banco de palabras
|
||||
class BancoPalabras {
|
||||
final Map<String, List<String>> categorias;
|
||||
|
||||
BancoPalabras(this.categorias);
|
||||
|
||||
static BancoPalabras? _instancia;
|
||||
|
||||
static Future<BancoPalabras> cargar() async {
|
||||
if (_instancia != null) return _instancia!;
|
||||
final jsonStr = await rootBundle.loadString('assets/palabras.json');
|
||||
final data = json.decode(jsonStr) as Map<String, dynamic>;
|
||||
final cats = data['categorias'] as Map<String, dynamic>;
|
||||
final mapa = <String, List<String>>{};
|
||||
for (final entrada in cats.entries) {
|
||||
mapa[entrada.key] = List<String>.from(entrada.value);
|
||||
}
|
||||
_instancia = BancoPalabras(mapa);
|
||||
return _instancia!;
|
||||
}
|
||||
|
||||
List<String> get nombresCategorias => categorias.keys.toList();
|
||||
|
||||
/// Obtiene una palabra aleatoria de la categoría dada (o de todas si es null)
|
||||
String palabraAleatoria(String? categoria) {
|
||||
final rng = Random();
|
||||
if (categoria == null || categoria == 'todas') {
|
||||
final todasPalabras = categorias.values.expand((l) => l).toList();
|
||||
return todasPalabras[rng.nextInt(todasPalabras.length)];
|
||||
}
|
||||
final lista = categorias[categoria]!;
|
||||
return lista[rng.nextInt(lista.length)];
|
||||
}
|
||||
|
||||
/// Devuelve la categoría a la que pertenece una palabra
|
||||
String? categoriaDepalabra(String palabra) {
|
||||
for (final entrada in categorias.entries) {
|
||||
if (entrada.value.contains(palabra)) return entrada.key;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String nombreBonitoCategoria(String clave) {
|
||||
const nombres = {
|
||||
'todas': 'Todas',
|
||||
'animales': 'Animales',
|
||||
'comida': 'Comida',
|
||||
'paises': 'Países',
|
||||
'deportes': 'Deportes',
|
||||
'profesiones': 'Profesiones',
|
||||
'objetos': 'Objetos',
|
||||
'lugares': 'Lugares',
|
||||
'peliculas': 'Películas',
|
||||
'musica': 'Música',
|
||||
'tecnologia': 'Tecnología',
|
||||
};
|
||||
return nombres[clave] ?? clave;
|
||||
}
|
||||
}
|
||||
79
lib/modelos/partida.dart
Normal file
79
lib/modelos/partida.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
import 'jugador.dart';
|
||||
|
||||
/// Configuración de una partida
|
||||
class ConfigPartida {
|
||||
final bool modoMultimovil;
|
||||
final String categoria; // 'todas' o nombre de categoría
|
||||
final int numImpostores;
|
||||
final bool pistaImpostor;
|
||||
final int? tiempoDebateSegundos; // null = sin límite
|
||||
|
||||
const ConfigPartida({
|
||||
this.modoMultimovil = false,
|
||||
this.categoria = 'todas',
|
||||
this.numImpostores = 1,
|
||||
this.pistaImpostor = false,
|
||||
this.tiempoDebateSegundos,
|
||||
});
|
||||
}
|
||||
|
||||
/// Fases del juego
|
||||
enum FaseJuego {
|
||||
configuracion,
|
||||
verPalabra,
|
||||
debate,
|
||||
votacion,
|
||||
resultado,
|
||||
adivinanza, // El impostor intenta adivinar la palabra
|
||||
finPartida,
|
||||
}
|
||||
|
||||
/// Resultado de una ronda de votación
|
||||
class ResultadoVotacion {
|
||||
final String eliminadoId;
|
||||
final String eliminadoNombre;
|
||||
final bool eraImpostor;
|
||||
final Map<String, String> votos; // votante -> votado
|
||||
|
||||
const ResultadoVotacion({
|
||||
required this.eliminadoId,
|
||||
required this.eliminadoNombre,
|
||||
required this.eraImpostor,
|
||||
required this.votos,
|
||||
});
|
||||
}
|
||||
|
||||
/// Estado completo de una partida
|
||||
class Partida {
|
||||
final ConfigPartida config;
|
||||
final List<Jugador> jugadores;
|
||||
final String palabraSecreta;
|
||||
final String categoriaReal;
|
||||
FaseJuego fase;
|
||||
int rondaActual;
|
||||
final List<ResultadoVotacion> historialVotaciones;
|
||||
String? ganador; // 'jugadores' | 'impostores' | null
|
||||
|
||||
Partida({
|
||||
required this.config,
|
||||
required this.jugadores,
|
||||
required this.palabraSecreta,
|
||||
required this.categoriaReal,
|
||||
this.fase = FaseJuego.verPalabra,
|
||||
this.rondaActual = 1,
|
||||
List<ResultadoVotacion>? historialVotaciones,
|
||||
this.ganador,
|
||||
}) : historialVotaciones = historialVotaciones ?? [];
|
||||
|
||||
List<Jugador> get jugadoresActivos =>
|
||||
jugadores.where((j) => !j.eliminado).toList();
|
||||
|
||||
List<Jugador> get impostoresActivos =>
|
||||
jugadoresActivos.where((j) => j.esImpostor).toList();
|
||||
|
||||
List<Jugador> get jugadoresNormalesActivos =>
|
||||
jugadoresActivos.where((j) => !j.esImpostor).toList();
|
||||
|
||||
int get impostoresTotales =>
|
||||
jugadores.where((j) => j.esImpostor).length;
|
||||
}
|
||||
Reference in New Issue
Block a user