feat: Implement multiplayer game session management
- 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:
@@ -3,11 +3,13 @@ import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:farolero/l10n/generated/app_localizations.dart';
|
||||
import '../modelos/jugador.dart';
|
||||
import '../modelos/inicio_partida_multijugador.dart';
|
||||
import '../modelos/usuario.dart';
|
||||
import '../servicios/servicio_nearby.dart';
|
||||
import '../servicios/servicio_permisos.dart';
|
||||
import '../tema/tema_app.dart';
|
||||
import 'pantalla_palabra_cliente.dart';
|
||||
import 'pantalla_palabras_cliente.dart';
|
||||
import 'pantalla_debate_cliente.dart';
|
||||
import 'pantalla_votacion_cliente.dart';
|
||||
|
||||
@@ -36,6 +38,7 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
|
||||
bool _esImpostor = false;
|
||||
String? _pistaCategoria;
|
||||
final List<Jugador> _jugadores = [];
|
||||
final List<JugadorInicioPartida> _jugadoresControlados = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -51,13 +54,38 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
|
||||
nearby.onMensaje((endpointId, mensaje) {
|
||||
if (mensaje.tipo == TipoMensaje.partidaInicio) {
|
||||
// El host ha iniciado la partida — nos ha enviado nuestra palabra
|
||||
final jugadoresData = mensaje.datos['jugadores'] as List<dynamic>?;
|
||||
final jugadoresTodosData =
|
||||
mensaje.datos['jugadoresTodos'] as List<dynamic>?;
|
||||
setState(() {
|
||||
_palabraRecibida = mensaje.datos['palabra'] as String?;
|
||||
_esImpostor = mensaje.datos['esImpostor'] as bool? ?? false;
|
||||
_jugadoresControlados
|
||||
..clear()
|
||||
..addAll(
|
||||
(jugadoresData ?? []).map(
|
||||
(json) => JugadorInicioPartida.fromJson(
|
||||
json as Map<String, dynamic>,
|
||||
),
|
||||
),
|
||||
);
|
||||
_jugadores
|
||||
..clear()
|
||||
..addAll(
|
||||
(jugadoresTodosData ?? []).map(
|
||||
(json) => Jugador.fromJson(json as Map<String, dynamic>),
|
||||
),
|
||||
);
|
||||
if (_jugadoresControlados.isNotEmpty) {
|
||||
final primero = _jugadoresControlados.first;
|
||||
_palabraRecibida = primero.palabra;
|
||||
_esImpostor = primero.esImpostor;
|
||||
} else {
|
||||
_palabraRecibida = mensaje.datos['palabra'] as String?;
|
||||
_esImpostor = mensaje.datos['esImpostor'] as bool? ?? false;
|
||||
}
|
||||
_pistaCategoria = mensaje.datos['categoria'] as String?;
|
||||
});
|
||||
// Navegar a pantalla de palabra del cliente
|
||||
if (mounted && _palabraRecibida != null) {
|
||||
if (mounted && (_jugadoresControlados.isNotEmpty || _palabraRecibida != null)) {
|
||||
_navegarAPalabra();
|
||||
}
|
||||
} else if (mensaje.tipo == TipoMensaje.fase) {
|
||||
@@ -71,6 +99,28 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
|
||||
}
|
||||
|
||||
void _navegarAPalabra() {
|
||||
if (_jugadoresControlados.isNotEmpty) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => PantallaPalabrasCliente(
|
||||
jugadores: List.unmodifiable(_jugadoresControlados),
|
||||
pistaCategoria: _pistaCategoria,
|
||||
onTodosVistos: () {
|
||||
final nearby = context.read<ServicioNearby>();
|
||||
if (nearby.hostEndpointId != null) {
|
||||
nearby.enviarMensaje(
|
||||
nearby.hostEndpointId!,
|
||||
MensajeP2P(tipo: TipoMensaje.listo, datos: {}),
|
||||
);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => PantallaPalabraCliente(
|
||||
@@ -121,16 +171,23 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
|
||||
MaterialPageRoute(
|
||||
builder: (_) => PantallaVotacionCliente(
|
||||
jugadores: _jugadores,
|
||||
onVoto: (votoporId) {
|
||||
jugadoresControlados: List.unmodifiable(_jugadoresControlados),
|
||||
onVotos: (votos) {
|
||||
final nearby = context.read<ServicioNearby>();
|
||||
if (nearby.hostEndpointId != null) {
|
||||
nearby.enviarMensaje(
|
||||
nearby.hostEndpointId!,
|
||||
MensajeP2P(
|
||||
tipo: TipoMensaje.voto,
|
||||
datos: {'votoporId': votoporId},
|
||||
),
|
||||
);
|
||||
for (final entry in votos.entries) {
|
||||
nearby.enviarMensaje(
|
||||
nearby.hostEndpointId!,
|
||||
MensajeP2P(
|
||||
tipo: TipoMensaje.voto,
|
||||
datos: {
|
||||
'votanteId': entry.key,
|
||||
'votadoId': entry.value,
|
||||
'votoporId': entry.value,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
@@ -605,19 +662,7 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
|
||||
),
|
||||
const Divider(),
|
||||
// Usuarios existentes
|
||||
...usuarios.map(
|
||||
(usuario) => ListTile(
|
||||
leading: Text(
|
||||
usuario.avatar ?? '👤',
|
||||
style: const TextStyle(fontSize: 24),
|
||||
),
|
||||
title: Text(usuario.nombre),
|
||||
onTap: () {
|
||||
// Seleccionar usuario - enviar al host
|
||||
_enviarUsuarioAlHost(usuario);
|
||||
},
|
||||
),
|
||||
),
|
||||
...usuarios.map(_buildUsuarioSalaTile),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -671,34 +716,57 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
|
||||
);
|
||||
|
||||
if (nombre != null && nombre.trim().isNotEmpty) {
|
||||
final nuevoUsuario = Usuario(
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
nombre: nombre.trim(),
|
||||
);
|
||||
// Agregar localmente
|
||||
nearby.agregarUsuario(nuevoUsuario);
|
||||
// Enviar al host
|
||||
_enviarUsuarioAlHost(nuevoUsuario);
|
||||
await nearby.crearUsuarioSala(nombre.trim(), seleccionar: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Envía el usuario seleccionado/creado al host
|
||||
/// Env?a el usuario seleccionado/creado al host
|
||||
void _enviarUsuarioAlHost(Usuario usuario) {
|
||||
final nearby = context.read<ServicioNearby>();
|
||||
if (nearby.hostEndpointId != null) {
|
||||
nearby.enviarMensaje(
|
||||
nearby.hostEndpointId!,
|
||||
MensajeP2P(
|
||||
tipo: TipoMensaje.usuarioNuevo,
|
||||
datos: {'usuario': usuario.toJson()},
|
||||
),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('${usuario.nombre} seleccionado')));
|
||||
}
|
||||
nearby.seleccionarUsuarioSala(usuario.id);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('${usuario.nombre} seleccionado')));
|
||||
}
|
||||
|
||||
Widget _buildUsuarioSalaTile(Usuario usuario) {
|
||||
final nearby = context.read<ServicioNearby>();
|
||||
final miClientId = nearby.miClientId;
|
||||
final seleccionadoPorMi = usuario.clienteIdSeleccionado == miClientId;
|
||||
final seleccionadoPorOtro =
|
||||
usuario.estaSeleccionado && usuario.clienteIdSeleccionado != miClientId;
|
||||
|
||||
return ListTile(
|
||||
leading: Text(
|
||||
usuario.avatar ?? '??',
|
||||
style: const TextStyle(fontSize: 24),
|
||||
),
|
||||
title: Text(usuario.nombre),
|
||||
subtitle: Text(
|
||||
seleccionadoPorMi
|
||||
? 'Seleccionado por este m?vil'
|
||||
: seleccionadoPorOtro
|
||||
? 'No disponible'
|
||||
: 'Disponible',
|
||||
),
|
||||
trailing: seleccionadoPorMi
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => nearby.liberarUsuarioSala(usuario.id),
|
||||
)
|
||||
: null,
|
||||
enabled: !seleccionadoPorOtro,
|
||||
onTap: seleccionadoPorOtro
|
||||
? null
|
||||
: () {
|
||||
if (seleccionadoPorMi) {
|
||||
nearby.liberarUsuarioSala(usuario.id);
|
||||
} else {
|
||||
_enviarUsuarioAlHost(usuario);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
// ==================== HELPERS ====================
|
||||
|
||||
Widget _buildError(String msg) {
|
||||
|
||||
Reference in New Issue
Block a user