import 'dart:math'; import 'package:flutter/foundation.dart'; import '../modelos/jugador.dart'; import '../modelos/partida.dart'; import '../modelos/palabra.dart'; import '../modelos/sala_multijugador.dart'; import '../servicios/servicio_notas.dart'; /// Estado global del juego gestionado con Provider class EstadoJuego extends ChangeNotifier { BancoPalabras? _banco; Partida? _partida; final Map _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 get votos => Map.unmodifiable(_votos); bool get cargando => _cargando; /// Jugador local del host (para modo multi-dispositivo) Jugador? get hostLocal => _hostLocal; Future 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 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 = {}; 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(); } /// Crea una partida multi-dispositivo usando los usuarios seleccionados de la /// sala como jugadores reales. La identidad de jugador se conserva por id y /// cada jugador queda asociado al endpoint del cliente que lo controla. void crearPartidaDesdeSala({ required ConfigPartida config, required EstadoSalaMultijugador sala, }) { if (_banco == null) return; final usuariosSeleccionados = sala.usuariosSeleccionados; if (usuariosSeleccionados.length < 3) return; final palabra = _banco!.palabraAleatoria(config.categoria); final categoriaReal = _banco!.categoriaDepalabra(palabra) ?? config.categoria; final jugadores = usuariosSeleccionados.map((usuario) { final clienteId = usuario.clienteIdSeleccionado; final endpointId = clienteId == null ? null : sala.clientes[clienteId]?.endpointId; return Jugador( id: usuario.id, nombre: usuario.nombre, endpointId: endpointId, ); }).toList(); final rng = Random.secure(); final numImpostores = config.numImpostores.clamp(1, jugadores.length ~/ 3); final impostoresElegidos = {}; while (impostoresElegidos.length < numImpostores) { impostoresElegidos.add(rng.nextInt(jugadores.length)); } for (final i in impostoresElegidos) { jugadores[i].esImpostor = true; } for (final jugador in jugadores) { if (!jugador.esImpostor) { jugador.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 = {}; 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(); } }