- Añadido modelo Usuario con pool de usuarios sincronizado - El host ahora recibe palabra y rol como cualquier jugador - UI de selección de perfil en pantallas de lobby - Los clientes pueden ver usuarios del servidor o crear nuevos - El juego no inicia hasta que el host selecciona perfil
237 lines
6.5 KiB
Dart
237 lines
6.5 KiB
Dart
import 'dart:math';
|
|
import 'package:flutter/foundation.dart';
|
|
import '../modelos/jugador.dart';
|
|
import '../modelos/partida.dart';
|
|
import '../modelos/palabra.dart';
|
|
import '../servicios/servicio_notas.dart';
|
|
|
|
/// Estado global del juego gestionado con Provider
|
|
class EstadoJuego extends ChangeNotifier {
|
|
BancoPalabras? _banco;
|
|
Partida? _partida;
|
|
final Map<String, String> _votos = {}; // votanteId -> votadoId
|
|
bool _cargando = false;
|
|
|
|
/// Jugador local del host en modo multi-dispositivo
|
|
Jugador? _hostLocal;
|
|
|
|
BancoPalabras? get banco => _banco;
|
|
Partida? get partida => _partida;
|
|
Map<String, String> get votos => Map.unmodifiable(_votos);
|
|
bool get cargando => _cargando;
|
|
|
|
/// Jugador local del host (para modo multi-dispositivo)
|
|
Jugador? get hostLocal => _hostLocal;
|
|
|
|
Future<void> cargarBanco() async {
|
|
_cargando = true;
|
|
notifyListeners();
|
|
_banco = await BancoPalabras.cargar();
|
|
_cargando = false;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Establece el jugador local del host para modo multi-dispositivo
|
|
void setHostJugador(String nombre) {
|
|
_hostLocal = Jugador(
|
|
id: 'host-local',
|
|
nombre: nombre,
|
|
endpointId: null, // El host local no tiene endpointId
|
|
);
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Crea una nueva partida con la configuración dada y lista de jugadores
|
|
void crearPartida({
|
|
required ConfigPartida config,
|
|
required List<String> nombresJugadores,
|
|
}) {
|
|
if (_banco == null) return;
|
|
if (nombresJugadores.length < 3) return;
|
|
|
|
// Seleccionar palabra
|
|
final palabra = _banco!.palabraAleatoria(config.categoria);
|
|
final categoriaReal =
|
|
_banco!.categoriaDepalabra(palabra) ?? config.categoria;
|
|
|
|
// Crear jugadores
|
|
final jugadores = nombresJugadores.asMap().entries.map((e) {
|
|
return Jugador(id: 'j${e.key}', nombre: e.value);
|
|
}).toList();
|
|
|
|
// Asignar impostores usando Random seguro (no predecible)
|
|
final rng = Random.secure();
|
|
final numImpostores = config.numImpostores.clamp(1, jugadores.length ~/ 3);
|
|
final impostoresElegidos = <int>{};
|
|
while (impostoresElegidos.length < numImpostores) {
|
|
impostoresElegidos.add(rng.nextInt(jugadores.length));
|
|
}
|
|
for (final i in impostoresElegidos) {
|
|
jugadores[i].esImpostor = true;
|
|
}
|
|
|
|
// Asignar palabras
|
|
for (final j in jugadores) {
|
|
if (!j.esImpostor) {
|
|
j.palabra = palabra;
|
|
}
|
|
}
|
|
|
|
_partida = Partida(
|
|
config: config,
|
|
jugadores: jugadores,
|
|
palabraSecreta: palabra,
|
|
categoriaReal: categoriaReal,
|
|
);
|
|
|
|
_votos.clear();
|
|
ServicioNotas.limpiarNotas();
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Avanza a la fase de debate
|
|
void iniciarDebate() {
|
|
if (_partida == null) return;
|
|
_partida!.fase = FaseJuego.debate;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Avanza a la fase de votación
|
|
void iniciarVotacion() {
|
|
if (_partida == null) return;
|
|
_partida!.fase = FaseJuego.votacion;
|
|
_votos.clear();
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Registra un voto (modo un solo móvil)
|
|
void registrarVoto(String votanteId, String votadoId) {
|
|
_votos[votanteId] = votadoId;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Elimina un voto
|
|
void eliminarVoto(String votanteId) {
|
|
_votos.remove(votanteId);
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Comprueba si todos los jugadores activos (no eliminados) han votado
|
|
bool todosHanVotado() {
|
|
if (_partida == null) return false;
|
|
final activos = _partida!.jugadoresActivos;
|
|
return activos.every((j) => _votos.containsKey(j.id));
|
|
}
|
|
|
|
/// Procesa los votos y determina el eliminado
|
|
ResultadoVotacion? procesarVotacion() {
|
|
if (_partida == null) return null;
|
|
|
|
// Contar votos
|
|
final conteo = <String, int>{};
|
|
for (final votado in _votos.values) {
|
|
conteo[votado] = (conteo[votado] ?? 0) + 1;
|
|
}
|
|
|
|
if (conteo.isEmpty) return null;
|
|
|
|
// Encontrar máximo
|
|
final maxVotos = conteo.values.reduce(max);
|
|
final masVotados = conteo.entries
|
|
.where((e) => e.value == maxVotos)
|
|
.toList();
|
|
|
|
// En caso de empate, elegir aleatoriamente (usar Random.secure para consistencia)
|
|
final rng = Random.secure();
|
|
final eliminadoId = masVotados[rng.nextInt(masVotados.length)].key;
|
|
|
|
final eliminado = _partida!.jugadores.firstWhere(
|
|
(j) => j.id == eliminadoId,
|
|
);
|
|
eliminado.eliminado = true;
|
|
|
|
final resultado = ResultadoVotacion(
|
|
eliminadoId: eliminadoId,
|
|
eliminadoNombre: eliminado.nombre,
|
|
eraImpostor: eliminado.esImpostor,
|
|
votos: Map.from(_votos),
|
|
);
|
|
|
|
_partida!.historialVotaciones.add(resultado);
|
|
_partida!.fase = FaseJuego.resultado;
|
|
notifyListeners();
|
|
|
|
return resultado;
|
|
}
|
|
|
|
/// Comprueba si la partida ha terminado y actualiza el estado
|
|
bool comprobarFinPartida() {
|
|
if (_partida == null) return false;
|
|
|
|
final impostoresVivos = _partida!.impostoresActivos.length;
|
|
final jugadoresVivos = _partida!.jugadoresNormalesActivos.length;
|
|
|
|
// Los jugadores ganan si no quedan impostores
|
|
if (impostoresVivos == 0) {
|
|
_partida!.ganador = 'jugadores';
|
|
_partida!.fase = FaseJuego.finPartida;
|
|
notifyListeners();
|
|
return true;
|
|
}
|
|
|
|
// Los impostores ganan si son >= que los jugadores normales
|
|
if (impostoresVivos >= jugadoresVivos) {
|
|
_partida!.ganador = 'impostores';
|
|
_partida!.fase = FaseJuego.finPartida;
|
|
notifyListeners();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Avanza a la fase de adivinanza del impostor
|
|
void iniciarAdivinanza() {
|
|
if (_partida == null) return;
|
|
_partida!.fase = FaseJuego.adivinanza;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// El impostor intenta adivinar la palabra
|
|
bool intentarAdivinar(String intento) {
|
|
if (_partida == null) return false;
|
|
final acierto =
|
|
intento.trim().toLowerCase() ==
|
|
_partida!.palabraSecreta.trim().toLowerCase();
|
|
if (acierto) {
|
|
_partida!.ganador = 'impostores';
|
|
_partida!.fase = FaseJuego.finPartida;
|
|
notifyListeners();
|
|
}
|
|
return acierto;
|
|
}
|
|
|
|
/// Inicia la siguiente ronda
|
|
void siguienteRonda() {
|
|
if (_partida == null) return;
|
|
_partida!.rondaActual++;
|
|
_partida!.fase = FaseJuego.debate;
|
|
_votos.clear();
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Revancha: mismos jugadores, nueva palabra
|
|
void revancha() {
|
|
if (_partida == null || _banco == null) return;
|
|
final nombres = _partida!.jugadores.map((j) => j.nombre).toList();
|
|
crearPartida(config: _partida!.config, nombresJugadores: nombres);
|
|
}
|
|
|
|
/// Limpia la partida actual
|
|
void limpiar() {
|
|
_partida = null;
|
|
_votos.clear();
|
|
notifyListeners();
|
|
}
|
|
}
|