feat: Implement multiplayer game session management
Some checks failed
Build & Deploy Farolero / Análisis de código (push) Has been cancelled
Build & Deploy Farolero / Build APK + AAB release (push) Has been cancelled

- Add models for managing player assignments and game session initialization in `inicio_partida_multijugador.dart`.
- Create a multiplayer room state management system in `sala_multijugador.dart`, including user registration, selection, and session validation.
- Develop a UI screen for displaying player words sequentially in `pantalla_palabras_cliente.dart`.
- Implement unit tests for the multiplayer session management and player assignment logic in `inicio_partida_multijugador_test.dart` and `sala_multijugador_test.dart`.
This commit is contained in:
Javier Bautista Fernández
2026-04-27 14:02:33 +02:00
parent 4a1abd0be0
commit a8d5b0f002
14 changed files with 1779 additions and 421 deletions

View File

@@ -1,9 +1,11 @@
import 'dart:convert';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:nearby_connections/nearby_connections.dart';
import '../modelos/inicio_partida_multijugador.dart';
import '../modelos/sala_multijugador.dart';
import '../modelos/usuario.dart';
/// Tipos de mensajes en el protocolo P2P
/// Tipos de mensajes en el protocolo P2P.
enum TipoMensaje {
salaInfo,
partidaInicio,
@@ -15,12 +17,18 @@ enum TipoMensaje {
listo,
ping,
jugadorDesconectado,
clienteRegistrado,
estadoSala,
crearUsuario,
seleccionarUsuario,
liberarUsuario,
errorOperacion,
usuarioNuevo,
usuarioEliminado,
usuariosActualizados,
}
/// Mensaje del protocolo P2P entre dispositivos
/// Mensaje del protocolo P2P entre dispositivos.
class MensajeP2P {
final TipoMensaje tipo;
final Map<String, dynamic> datos;
@@ -40,7 +48,8 @@ class MensajeP2P {
Uint8List toBytes() => Uint8List.fromList(utf8.encode(toJson()));
}
/// Info de un jugador conectado
/// Info de un dispositivo conectado. El nombre identifica al cliente/dispositivo,
/// no necesariamente a un jugador de la partida.
class JugadorConectado {
final String endpointId;
final String nombre;
@@ -53,13 +62,13 @@ class JugadorConectado {
});
}
/// Callback para mensajes recibidos
typedef OnMensajeCallback =
void Function(String endpointId, MensajeP2P mensaje);
/// Servicio para conexiones P2P usando Google Nearby Connections API.
class ServicioNearby extends ChangeNotifier {
static const _serviceId = 'es.freetimelab.farolero';
static const _hostClientId = 'host';
bool _esHost = false;
bool _conectado = false;
@@ -67,23 +76,21 @@ class ServicioNearby extends ChangeNotifier {
bool _anunciando = false;
String? _miEndpointId;
String? _hostEndpointId;
String? _roomId;
String? _miClientId;
String? _nombreSala;
String? _miNombre;
final Map<String, JugadorConectado> _jugadores = {};
final List<OnMensajeCallback> _listeners = [];
final Map<String, String> _hostsEncontrados = {};
final Map<String, Usuario> _usuariosPool = {};
// Hosts descubiertos (para discovery automático)
final Map<String, String> _hostsEncontrados = {}; // endpointId -> nombre
// Estado para clientes
String? _palabraRecibida;
bool? _soyImpostor;
String? _faseActual;
Map<String, dynamic>? _datosPartida;
// Pool de usuarios para modo multi-dispositivo
final Map<String, Usuario> _usuariosPool = {};
EstadoSalaMultijugador? _estadoSala;
bool get esHost => _esHost;
bool get conectado => _conectado;
@@ -91,27 +98,35 @@ class ServicioNearby extends ChangeNotifier {
bool get anunciando => _anunciando;
String? get miEndpointId => _miEndpointId;
String? get hostEndpointId => _hostEndpointId;
String? get roomId => _roomId;
String? get miClientId => _miClientId;
String? get nombreSala => _nombreSala;
String? get miNombre => _miNombre;
String? get palabraRecibida => _palabraRecibida;
bool? get soyImpostor => _soyImpostor;
String? get faseActual => _faseActual;
Map<String, dynamic>? get datosPartida => _datosPartida;
EstadoSalaMultijugador? get estadoSala => _estadoSala;
List<JugadorConectado> get jugadores => _jugadores.values.toList();
int get numJugadoresConectados => _jugadores.length;
Map<String, String> get hostsEncontrados =>
Map.unmodifiable(_hostsEncontrados);
/// Pool de usuarios disponibles para seleccionar en modo multi-dispositivo
List<Usuario> get usuarios => _usuariosPool.values.toList();
List<Usuario> get usuarios =>
(_estadoSala?.usuarios.values ?? _usuariosPool.values).toList();
List<Usuario> get misUsuariosSeleccionados {
final clientId = _miClientId;
final sala = _estadoSala;
if (clientId == null || sala == null) return [];
return sala.usuariosPorCliente(clientId);
}
/// Registra un listener de mensajes
void onMensaje(OnMensajeCallback callback) {
_listeners.add(callback);
}
/// Elimina un listener
void removeMensajeListener(OnMensajeCallback callback) {
_listeners.remove(callback);
}
@@ -122,26 +137,24 @@ class ServicioNearby extends ChangeNotifier {
}
}
// ==================== USER POOL ====================
// ==================== USER POOL / SALA ====================
/// Agrega un usuario al pool de usuarios
void agregarUsuario(Usuario usuario) {
_usuariosPool[usuario.id] = usuario;
_estadoSala?.usuarios[usuario.id] = usuario;
notifyListeners();
}
/// Elimina un usuario del pool
void eliminarUsuario(String usuarioId) {
_usuariosPool.remove(usuarioId);
_estadoSala?.usuarios.remove(usuarioId);
notifyListeners();
}
/// Obtiene un usuario por su ID
Usuario? getUsuario(String usuarioId) {
return _usuariosPool[usuarioId];
return _estadoSala?.usuarios[usuarioId] ?? _usuariosPool[usuarioId];
}
/// Sincroniza el pool de usuarios con una lista
void sincronizarUsuarios(List<Usuario> usuarios) {
_usuariosPool.clear();
for (final usuario in usuarios) {
@@ -150,12 +163,38 @@ class ServicioNearby extends ChangeNotifier {
notifyListeners();
}
/// Obtiene el jugador local del host (él mismo como participante)
/// Retorna un JugadorConectado con endpointId null porque es local
void _sincronizarSala(EstadoSalaMultijugador sala) {
_estadoSala = sala;
_roomId = sala.roomId;
_usuariosPool
..clear()
..addEntries(sala.usuarios.entries);
}
void _sincronizarPoolDesdeSala() {
final sala = _estadoSala;
if (sala == null) return;
_usuariosPool
..clear()
..addEntries(sala.usuarios.entries);
}
Future<void> _broadcastEstadoSala() async {
final sala = _estadoSala;
if (sala == null) return;
_sincronizarPoolDesdeSala();
if (_esHost) {
await enviarATodos(
MensajeP2P(tipo: TipoMensaje.estadoSala, datos: sala.toJson()),
);
}
notifyListeners();
}
JugadorConectado? getJugadorLocal() {
if (_miNombre == null) return null;
return JugadorConectado(
endpointId: _miEndpointId ?? '', // vacío indica que es el host local
endpointId: _miEndpointId ?? '',
nombre: _miNombre!,
listo: true,
);
@@ -163,10 +202,29 @@ class ServicioNearby extends ChangeNotifier {
// ==================== HOST ====================
/// Inicia como host (anunciando el endpoint)
Future<bool> iniciarHost(String nombreSala, String miNombre) async {
_nombreSala = nombreSala;
_miNombre = miNombre;
_roomId = DateTime.now().microsecondsSinceEpoch.toString();
_miClientId = _hostClientId;
_estadoSala = EstadoSalaMultijugador.crear(
roomId: _roomId!,
nombreSala: nombreSala,
hostClientId: _hostClientId,
hostNombre: miNombre,
);
// Compatibilidad con el flujo actual: el nombre con el que se crea la sala
// arranca como usuario seleccionado por el host. Luego puede crear/seleccionar
// más usuarios en el lobby.
final usuarioHost = Usuario(
id: 'u-${_roomId!}-host',
nombre: miNombre,
creadoPorClienteId: _hostClientId,
clienteIdSeleccionado: _hostClientId,
);
_estadoSala!.crearUsuario(usuarioHost);
_sincronizarPoolDesdeSala();
try {
final resultado = await Nearby().startAdvertising(
@@ -194,7 +252,6 @@ class ServicioNearby extends ChangeNotifier {
// ==================== CLIENTE ====================
/// Busca hosts disponibles
Future<bool> buscarHosts(String miNombre) async {
_miNombre = miNombre;
@@ -219,7 +276,6 @@ class ServicioNearby extends ChangeNotifier {
}
}
/// Conecta a un host específico
Future<bool> conectarAHost(String endpointId, String miNombre) async {
try {
await Nearby().requestConnection(
@@ -239,8 +295,7 @@ class ServicioNearby extends ChangeNotifier {
// ==================== CALLBACKS NEARBY ====================
void _onConexionIniciada(String endpointId, ConnectionInfo info) {
debugPrint('Conexión iniciada con $endpointId: ${info.endpointName}');
// Auto-aceptar conexiones
debugPrint('Conexion iniciada con $endpointId: ${info.endpointName}');
Nearby().acceptConnection(
endpointId,
onPayLoadRecieved: _onPayloadRecibido,
@@ -249,16 +304,13 @@ class ServicioNearby extends ChangeNotifier {
}
void _onResultadoConexion(String endpointId, Status status) {
debugPrint('Resultado conexión $endpointId: $status');
debugPrint('Resultado conexion $endpointId: $status');
if (status == Status.CONNECTED) {
if (_esHost) {
// Host: esperar mensaje 'unirse' del cliente
debugPrint('Cliente conectado: $endpointId');
} else {
// Cliente: conectado al host
_hostEndpointId = endpointId;
_conectado = true;
// Enviar mensaje de unirse
enviarMensaje(
endpointId,
MensajeP2P(
@@ -269,16 +321,20 @@ class ServicioNearby extends ChangeNotifier {
}
notifyListeners();
} else {
debugPrint('Conexión fallida con $endpointId');
debugPrint('Conexion fallida con $endpointId');
}
}
void _onDesconexion(String endpointId) {
debugPrint('Desconexión: $endpointId');
debugPrint('Desconexion: $endpointId');
if (_esHost) {
final jugador = _jugadores.remove(endpointId);
final cliente = _estadoSala?.clientePorEndpoint(endpointId);
if (cliente != null) {
_estadoSala?.desconectarCliente(cliente.clientId);
_broadcastEstadoSala();
}
if (jugador != null) {
// Notificar a todos que se desconectó
enviarATodos(
MensajeP2P(
tipo: TipoMensaje.jugadorDesconectado,
@@ -287,7 +343,6 @@ class ServicioNearby extends ChangeNotifier {
);
}
} else {
// Cliente perdió conexión con host
_conectado = false;
_hostEndpointId = null;
}
@@ -312,7 +367,6 @@ class ServicioNearby extends ChangeNotifier {
}
}
/// Para el discovery sin desconectar
Future<void> pararBusqueda() async {
try {
await Nearby().stopDiscovery();
@@ -334,9 +388,7 @@ class ServicioNearby extends ChangeNotifier {
}
}
void _onPayloadUpdate(String endpointId, PayloadTransferUpdate update) {
// No necesitamos trackear progreso para bytes pequeños
}
void _onPayloadUpdate(String endpointId, PayloadTransferUpdate update) {}
// ==================== PROCESAMIENTO DE MENSAJES ====================
@@ -355,33 +407,11 @@ class ServicioNearby extends ChangeNotifier {
void _procesarMensajeHost(String endpointId, MensajeP2P mensaje) {
switch (mensaje.tipo) {
case TipoMensaje.unirse:
final nombre = mensaje.datos['nombre'] as String? ?? 'Jugador';
_jugadores[endpointId] = JugadorConectado(
endpointId: endpointId,
nombre: nombre,
);
// Enviar info de sala al nuevo jugador (incluye pool de usuarios)
enviarMensaje(
endpointId,
MensajeP2P(
tipo: TipoMensaje.salaInfo,
datos: {
'sala': _nombreSala,
'jugadores': _jugadores.values
.map((j) => {'nombre': j.nombre, 'endpointId': j.endpointId})
.toList(),
'usuarios': _usuariosPool.values.map((u) => u.toJson()).toList(),
},
),
);
notifyListeners();
_registrarClienteRemoto(endpointId, mensaje);
break;
case TipoMensaje.voto:
// Propagar al flujo de juego
_notificarMensaje(endpointId, mensaje);
break;
case TipoMensaje.listo:
final jugador = _jugadores[endpointId];
if (jugador != null) {
@@ -389,33 +419,68 @@ class ServicioNearby extends ChangeNotifier {
notifyListeners();
}
break;
case TipoMensaje.crearUsuario:
_handleCrearUsuario(endpointId, mensaje);
break;
case TipoMensaje.seleccionarUsuario:
_handleSeleccionarUsuario(endpointId, mensaje);
break;
case TipoMensaje.liberarUsuario:
_handleLiberarUsuario(endpointId, mensaje);
break;
case TipoMensaje.eliminarUsuario:
_handleEliminarUsuario(endpointId, mensaje);
break;
case TipoMensaje.usuarioNuevo:
_handleUsuarioNuevo(mensaje);
break;
case TipoMensaje.usuariosActualizados:
_handleUsuariosActualizados(mensaje);
break;
default:
break;
}
}
void _registrarClienteRemoto(String endpointId, MensajeP2P mensaje) {
final nombre = mensaje.datos['nombre'] as String? ?? 'Jugador';
final clientId = endpointId;
_jugadores[endpointId] = JugadorConectado(
endpointId: endpointId,
nombre: nombre,
);
_estadoSala?.registrarCliente(
ClienteSala(clientId: clientId, endpointId: endpointId, nombre: nombre),
);
enviarMensaje(
endpointId,
MensajeP2P(
tipo: TipoMensaje.clienteRegistrado,
datos: {
'clientId': clientId,
'sala': _nombreSala,
'roomId': _roomId,
'jugadores': _jugadores.values
.map((j) => {'nombre': j.nombre, 'endpointId': j.endpointId})
.toList(),
'usuarios': _usuariosPool.values.map((u) => u.toJson()).toList(),
if (_estadoSala != null) 'estadoSala': _estadoSala!.toJson(),
},
),
);
_broadcastEstadoSala();
notifyListeners();
}
void _handleUsuarioNuevo(MensajeP2P mensaje) {
final usuarioJson = mensaje.datos['usuario'] as Map<String, dynamic>?;
if (usuarioJson != null) {
final nuevoUsuario = Usuario.fromJson(usuarioJson);
_usuariosPool[nuevoUsuario.id] = nuevoUsuario;
// Propagar a todos los clientes
_estadoSala?.usuarios[nuevoUsuario.id] = nuevoUsuario;
if (_esHost) {
enviarATodos(
MensajeP2P(
tipo: TipoMensaje.usuarioNuevo,
datos: {'usuario': usuarioJson},
),
);
_broadcastEstadoSala();
}
notifyListeners();
}
@@ -433,11 +498,87 @@ class ServicioNearby extends ChangeNotifier {
}
}
String _clientIdParaEndpoint(String endpointId, MensajeP2P mensaje) {
return mensaje.datos['clientId'] as String? ??
_estadoSala?.clientePorEndpoint(endpointId)?.clientId ??
endpointId;
}
Future<void> _enviarErrorOperacion(
String endpointId,
ResultadoOperacionSala resultado,
) async {
if (resultado.exitoso) return;
await enviarMensaje(
endpointId,
MensajeP2P(tipo: TipoMensaje.errorOperacion, datos: resultado.toJson()),
);
}
void _handleCrearUsuario(String endpointId, MensajeP2P mensaje) {
final sala = _estadoSala;
final usuarioJson = mensaje.datos['usuario'] as Map<String, dynamic>?;
if (sala == null || usuarioJson == null) return;
final clientId = _clientIdParaEndpoint(endpointId, mensaje);
final usuario = Usuario.fromJson(usuarioJson).copiar(
creadoPorClienteId: clientId,
liberarSeleccion: true,
);
final resultadoCrear = sala.crearUsuario(usuario);
if (!resultadoCrear.exitoso) {
_enviarErrorOperacion(endpointId, resultadoCrear);
return;
}
if (mensaje.datos['seleccionar'] == true) {
final resultadoSeleccion = sala.seleccionarUsuario(
usuarioId: usuario.id,
clienteId: clientId,
);
_enviarErrorOperacion(endpointId, resultadoSeleccion);
}
_broadcastEstadoSala();
}
void _handleSeleccionarUsuario(String endpointId, MensajeP2P mensaje) {
final sala = _estadoSala;
final usuarioId = mensaje.datos['usuarioId'] as String?;
if (sala == null || usuarioId == null) return;
final resultado = sala.seleccionarUsuario(
usuarioId: usuarioId,
clienteId: _clientIdParaEndpoint(endpointId, mensaje),
);
_enviarErrorOperacion(endpointId, resultado);
_broadcastEstadoSala();
}
void _handleLiberarUsuario(String endpointId, MensajeP2P mensaje) {
final sala = _estadoSala;
final usuarioId = mensaje.datos['usuarioId'] as String?;
if (sala == null || usuarioId == null) return;
final resultado = sala.liberarUsuario(
usuarioId: usuarioId,
solicitanteClientId: _clientIdParaEndpoint(endpointId, mensaje),
);
_enviarErrorOperacion(endpointId, resultado);
_broadcastEstadoSala();
}
void _handleEliminarUsuario(String endpointId, MensajeP2P mensaje) {
final sala = _estadoSala;
final usuarioId = mensaje.datos['usuarioId'] as String?;
if (sala == null || usuarioId == null) return;
final resultado = sala.eliminarUsuario(
usuarioId: usuarioId,
solicitanteClientId: _clientIdParaEndpoint(endpointId, mensaje),
);
_enviarErrorOperacion(endpointId, resultado);
_broadcastEstadoSala();
}
void _procesarMensajeCliente(String endpointId, MensajeP2P mensaje) {
switch (mensaje.tipo) {
case TipoMensaje.salaInfo:
_datosPartida = mensaje.datos;
// Sincronizar pool de usuarios si viene en el mensaje
final usuariosData = mensaje.datos['usuarios'] as List<dynamic>?;
if (usuariosData != null) {
_usuariosPool.clear();
@@ -448,42 +589,54 @@ class ServicioNearby extends ChangeNotifier {
}
notifyListeners();
break;
case TipoMensaje.clienteRegistrado:
_miClientId = mensaje.datos['clientId'] as String?;
_datosPartida = mensaje.datos;
final estadoSalaJson =
mensaje.datos['estadoSala'] as Map<String, dynamic>?;
if (estadoSalaJson != null) {
_sincronizarSala(EstadoSalaMultijugador.fromJson(estadoSalaJson));
}
notifyListeners();
break;
case TipoMensaje.estadoSala:
_sincronizarSala(EstadoSalaMultijugador.fromJson(mensaje.datos));
notifyListeners();
break;
case TipoMensaje.partidaInicio:
_palabraRecibida = mensaje.datos['palabra'] as String?;
_soyImpostor = mensaje.datos['esImpostor'] as bool? ?? false;
final jugadoresInicio = mensaje.datos['jugadores'] as List<dynamic>?;
if (jugadoresInicio != null && jugadoresInicio.isNotEmpty) {
final primerJugador = jugadoresInicio.first as Map<String, dynamic>;
_palabraRecibida = primerJugador['palabra'] as String?;
_soyImpostor = primerJugador['esImpostor'] as bool? ?? false;
} else {
_palabraRecibida = mensaje.datos['palabra'] as String?;
_soyImpostor = mensaje.datos['esImpostor'] as bool? ?? false;
}
_datosPartida = mensaje.datos;
notifyListeners();
break;
case TipoMensaje.fase:
_faseActual = mensaje.datos['fase'] as String?;
_datosPartida = mensaje.datos;
notifyListeners();
break;
case TipoMensaje.votacionResultado:
_datosPartida = mensaje.datos;
notifyListeners();
break;
case TipoMensaje.partidaFin:
case TipoMensaje.errorOperacion:
_datosPartida = mensaje.datos;
notifyListeners();
break;
case TipoMensaje.jugadorDesconectado:
notifyListeners();
break;
default:
break;
}
}
// ==================== ENVÍO ====================
// ==================== ENVIO ====================
/// Envía un mensaje a un dispositivo específico
Future<void> enviarMensaje(String endpointId, MensajeP2P mensaje) async {
try {
await Nearby().sendBytesPayload(endpointId, mensaje.toBytes());
@@ -492,20 +645,113 @@ class ServicioNearby extends ChangeNotifier {
}
}
/// Envía un mensaje a todos los dispositivos conectados (solo host)
Future<void> enviarATodos(MensajeP2P mensaje) async {
for (final id in _jugadores.keys) {
await enviarMensaje(id, mensaje);
}
}
Future<void> crearUsuarioSala(String nombre, {bool seleccionar = true}) async {
final nombreLimpio = nombre.trim();
if (nombreLimpio.isEmpty) return;
final clientId = _miClientId;
final usuario = Usuario(
id: 'u-${DateTime.now().microsecondsSinceEpoch}',
nombre: nombreLimpio,
creadoPorClienteId: clientId,
);
if (_esHost && _estadoSala != null && clientId != null) {
final resultado = _estadoSala!.crearUsuario(usuario);
if (resultado.exitoso && seleccionar) {
_estadoSala!.seleccionarUsuario(
usuarioId: usuario.id,
clienteId: clientId,
);
}
await _broadcastEstadoSala();
return;
}
final hostId = _hostEndpointId;
if (hostId == null) return;
await enviarMensaje(
hostId,
MensajeP2P(
tipo: TipoMensaje.crearUsuario,
datos: {
'clientId': clientId,
'seleccionar': seleccionar,
'usuario': usuario.toJson(),
},
),
);
}
Future<void> seleccionarUsuarioSala(String usuarioId) async {
final clientId = _miClientId;
if (_esHost && _estadoSala != null && clientId != null) {
_estadoSala!.seleccionarUsuario(usuarioId: usuarioId, clienteId: clientId);
await _broadcastEstadoSala();
return;
}
final hostId = _hostEndpointId;
if (hostId == null) return;
await enviarMensaje(
hostId,
MensajeP2P(
tipo: TipoMensaje.seleccionarUsuario,
datos: {'clientId': clientId, 'usuarioId': usuarioId},
),
);
}
Future<void> liberarUsuarioSala(String usuarioId) async {
final clientId = _miClientId;
if (_esHost && _estadoSala != null && clientId != null) {
_estadoSala!.liberarUsuario(
usuarioId: usuarioId,
solicitanteClientId: clientId,
);
await _broadcastEstadoSala();
return;
}
final hostId = _hostEndpointId;
if (hostId == null) return;
await enviarMensaje(
hostId,
MensajeP2P(
tipo: TipoMensaje.liberarUsuario,
datos: {'clientId': clientId, 'usuarioId': usuarioId},
),
);
}
Future<void> eliminarUsuarioSala(String usuarioId) async {
final clientId = _miClientId;
if (_esHost && _estadoSala != null && clientId != null) {
_estadoSala!.eliminarUsuario(
usuarioId: usuarioId,
solicitanteClientId: clientId,
);
await _broadcastEstadoSala();
return;
}
final hostId = _hostEndpointId;
if (hostId == null) return;
await enviarMensaje(
hostId,
MensajeP2P(
tipo: TipoMensaje.eliminarUsuario,
datos: {'clientId': clientId, 'usuarioId': usuarioId},
),
);
}
// ==================== HOST: ACCIONES DE JUEGO ====================
/// Host envía inicio de partida con la palabra de cada jugador
Future<void> enviarInicioPartida({
required String palabraSecreta,
required String categoria,
required Map<String, bool> impostores, // endpointId -> esImpostor
required Map<String, bool> impostores,
}) async {
for (final entry in _jugadores.entries) {
final esImpostor = impostores[entry.key] ?? false;
@@ -517,14 +763,39 @@ class ServicioNearby extends ChangeNotifier {
'palabra': esImpostor ? null : palabraSecreta,
'esImpostor': esImpostor,
'categoria': categoria,
'numJugadores': _jugadores.length + 1, // +1 por el host
'numJugadores': _jugadores.length + 1,
},
),
);
}
}
/// Host envía cambio de fase
Future<void> enviarInicioPartidaMulti({
required List<AsignacionJugador> asignaciones,
required String palabraSecreta,
required String categoria,
required Map<String, bool> impostoresPorJugadorId,
required List<Map<String, dynamic>> jugadoresTodos,
}) async {
final payloads = InicioPartidaMultijugador.crearPayloadsPorCliente(
asignaciones: asignaciones,
palabraSecreta: palabraSecreta,
categoria: categoria,
impostoresPorJugadorId: impostoresPorJugadorId,
);
for (final payload in payloads.values) {
final endpointId = payload.endpointId;
if (endpointId == null) continue;
final datos = payload.toJson();
datos['jugadoresTodos'] = jugadoresTodos;
await enviarMensaje(
endpointId,
MensajeP2P(tipo: TipoMensaje.partidaInicio, datos: datos),
);
}
}
Future<void> enviarCambioFase(
String fase, [
Map<String, dynamic>? extra,
@@ -533,14 +804,12 @@ class ServicioNearby extends ChangeNotifier {
await enviarATodos(MensajeP2P(tipo: TipoMensaje.fase, datos: datos));
}
/// Host envía resultado de votación
Future<void> enviarResultadoVotacion(Map<String, dynamic> resultado) async {
await enviarATodos(
MensajeP2P(tipo: TipoMensaje.votacionResultado, datos: resultado),
);
}
/// Host envía fin de partida
Future<void> enviarFinPartida(Map<String, dynamic> resultado) async {
await enviarATodos(
MensajeP2P(tipo: TipoMensaje.partidaFin, datos: resultado),
@@ -549,7 +818,6 @@ class ServicioNearby extends ChangeNotifier {
// ==================== LIMPIEZA ====================
/// Desconecta y limpia todo
Future<void> desconectar() async {
try {
await Nearby().stopAllEndpoints();
@@ -565,27 +833,30 @@ class ServicioNearby extends ChangeNotifier {
_anunciando = false;
_miEndpointId = null;
_hostEndpointId = null;
_roomId = null;
_miClientId = null;
_nombreSala = null;
_miNombre = null;
_palabraRecibida = null;
_soyImpostor = null;
_faseActual = null;
_datosPartida = null;
_estadoSala = null;
_jugadores.clear();
_hostsEncontrados.clear();
_usuariosPool.clear();
notifyListeners();
}
/// Genera los datos para el código QR de conexión
String generarDatosQR(String nombreSala) {
return json.encode({
'app': 'farolero',
'sala': nombreSala,
'host': _miNombre,
'roomId': _roomId,
});
}
/// Parsea datos de QR escaneado
static Map<String, dynamic>? parsearQR(String datos) {
try {
final mapa = json.decode(datos) as Map<String, dynamic>;