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,9 +3,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:farolero/l10n/generated/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../estado/estado_juego.dart';
|
||||
import '../modelos/inicio_partida_multijugador.dart';
|
||||
import '../modelos/partida.dart';
|
||||
import '../servicios/servicio_nearby.dart';
|
||||
import '../tema/tema_app.dart';
|
||||
import 'pantalla_votacion_cliente.dart';
|
||||
import 'pantalla_palabras_cliente.dart';
|
||||
|
||||
class PantallaGestorHost extends StatefulWidget {
|
||||
final VoidCallback onPartidaFin;
|
||||
@@ -51,8 +54,11 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
|
||||
setState(() => _clientesListos[endpointId] = true);
|
||||
} else if (mensaje.tipo == TipoMensaje.voto) {
|
||||
final votanteId = mensaje.datos['votanteId'] as String?;
|
||||
final votoId = mensaje.datos['votoporId'] as String?;
|
||||
final votoId =
|
||||
mensaje.datos['votadoId'] as String? ??
|
||||
mensaje.datos['votoporId'] as String?;
|
||||
if (votanteId != null && votoId != null) {
|
||||
context.read<EstadoJuego>().registrarVoto(votanteId, votoId);
|
||||
setState(() => _votosRecibidos[votanteId] = votoId);
|
||||
}
|
||||
}
|
||||
@@ -116,9 +122,8 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
|
||||
);
|
||||
}
|
||||
|
||||
final numJugadores = partida.jugadores.length + 1;
|
||||
final todosListos = _clientesListos.length >= numJugadores - 1;
|
||||
final todosVotaron = _votosRecibidos.length >= numJugadores - 1;
|
||||
final todosListos = _clientesListos.length >= nearby.jugadores.length;
|
||||
final todosVotaron = estado.todosHanVotado();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
@@ -294,14 +299,45 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
|
||||
|
||||
void _mostrarPalabraHost(BuildContext context) {
|
||||
final estado = context.read<EstadoJuego>();
|
||||
final sala = context.read<ServicioNearby>().estadoSala;
|
||||
final partida = estado.partida;
|
||||
if (partida == null) return;
|
||||
if (partida == null || sala == null) return;
|
||||
|
||||
// Buscar el jugador host local
|
||||
final hostLocal = partida.jugadores.firstWhere(
|
||||
(j) => j.nombre == context.read<ServicioNearby>().miNombre,
|
||||
orElse: () => partida.jugadores.first,
|
||||
);
|
||||
final jugadoresHost = sala
|
||||
.usuariosPorCliente(sala.hostClientId)
|
||||
.where((usuario) => partida.jugadores.any((j) => j.id == usuario.id))
|
||||
.map((usuario) {
|
||||
final jugador = partida.jugadores.firstWhere(
|
||||
(j) => j.id == usuario.id,
|
||||
);
|
||||
return JugadorInicioPartida(
|
||||
jugadorId: jugador.id,
|
||||
nombre: jugador.nombre,
|
||||
esImpostor: jugador.esImpostor,
|
||||
palabra: jugador.palabra,
|
||||
);
|
||||
})
|
||||
.toList();
|
||||
|
||||
if (jugadoresHost.length > 1) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => PantallaPalabrasCliente(
|
||||
jugadores: jugadoresHost,
|
||||
pistaCategoria: partida.config.pistaImpostor
|
||||
? partida.categoriaReal
|
||||
: null,
|
||||
onTodosVistos: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final hostLocal = jugadoresHost.isNotEmpty
|
||||
? partida.jugadores.firstWhere((j) => j.id == jugadoresHost.first.jugadorId)
|
||||
: partida.jugadores.first;
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
@@ -393,6 +429,12 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
|
||||
bool todosVotaron,
|
||||
ServicioNearby nearby,
|
||||
) {
|
||||
final estado = context.watch<EstadoJuego>();
|
||||
final partida = estado.partida!;
|
||||
final totalVotos = partida.jugadoresActivos.length;
|
||||
final votosEmitidos = estado.votos.length;
|
||||
final progreso = totalVotos == 0 ? 0.0 : votosEmitidos / totalVotos;
|
||||
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -410,19 +452,12 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
l10n.votesProgress(
|
||||
_votosRecibidos.length,
|
||||
nearby.jugadores.length + 1,
|
||||
),
|
||||
),
|
||||
Text(l10n.votesProgress(votosEmitidos, totalVotos)),
|
||||
const SizedBox(height: 8),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value:
|
||||
_votosRecibidos.length /
|
||||
(nearby.jugadores.length + 1),
|
||||
value: progreso.clamp(0.0, 1.0).toDouble(),
|
||||
backgroundColor: TemaApp.colorSuperficie,
|
||||
valueColor: const AlwaysStoppedAnimation(
|
||||
TemaApp.colorAcento,
|
||||
@@ -433,6 +468,19 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: _hostYaVoto(context) ? null : () => _abrirVotacionHost(context),
|
||||
icon: const Icon(Icons.how_to_vote),
|
||||
label: Text(
|
||||
_hostYaVoto(context)
|
||||
? 'Votos del host registrados'
|
||||
: 'Votar por los jugadores de este m?vil',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
l10n.playersVoted,
|
||||
@@ -441,15 +489,11 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: nearby.jugadores.length + 1,
|
||||
itemCount: partida.jugadoresActivos.length,
|
||||
itemBuilder: (context, index) {
|
||||
final esHost = index == 0;
|
||||
final nombre = esHost
|
||||
? (nearby.miNombre ?? 'Host')
|
||||
: nearby.jugadores[index - 1].nombre;
|
||||
final haVotado =
|
||||
esHost || _votosRecibidos.containsKey(nombre);
|
||||
return _buildJugadorTile(nombre, esHost, haVotado);
|
||||
final jugador = partida.jugadoresActivos[index];
|
||||
final haVotado = estado.votos.containsKey(jugador.id);
|
||||
return _buildJugadorTile(jugador.nombre, false, haVotado);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -478,6 +522,51 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
|
||||
);
|
||||
}
|
||||
|
||||
bool _hostYaVoto(BuildContext context) {
|
||||
final estado = context.read<EstadoJuego>();
|
||||
final sala = context.read<ServicioNearby>().estadoSala;
|
||||
if (sala == null || estado.partida == null) return false;
|
||||
final hostIds = sala.usuariosPorCliente(sala.hostClientId).map((u) => u.id);
|
||||
return hostIds.every((id) => estado.votos.containsKey(id));
|
||||
}
|
||||
|
||||
void _abrirVotacionHost(BuildContext context) {
|
||||
final estado = context.read<EstadoJuego>();
|
||||
final sala = context.read<ServicioNearby>().estadoSala;
|
||||
final partida = estado.partida;
|
||||
if (sala == null || partida == null) return;
|
||||
|
||||
final jugadoresHost = sala.usuariosPorCliente(sala.hostClientId)
|
||||
.where((usuario) => partida.jugadoresActivos.any((j) => j.id == usuario.id))
|
||||
.map(
|
||||
(usuario) => JugadorInicioPartida(
|
||||
jugadorId: usuario.id,
|
||||
nombre: usuario.nombre,
|
||||
esImpostor: partida.jugadores.firstWhere((j) => j.id == usuario.id).esImpostor,
|
||||
palabra: partida.jugadores.firstWhere((j) => j.id == usuario.id).palabra,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => PantallaVotacionCliente(
|
||||
jugadores: partida.jugadoresActivos,
|
||||
jugadoresControlados: jugadoresHost,
|
||||
onVotos: (votos) {
|
||||
for (final entry in votos.entries) {
|
||||
estado.registrarVoto(entry.key, entry.value);
|
||||
_votosRecibidos[entry.key] = entry.value;
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildJugadorTile(String nombre, bool esHost, bool listo) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
|
||||
Reference in New Issue
Block a user