Files
farolero/lib/modelos/sala_multijugador.dart
Javier Bautista Fernández a8d5b0f002
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
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`.
2026-04-27 14:02:33 +02:00

295 lines
8.9 KiB
Dart

import 'usuario.dart';
enum FaseSalaMultijugador { lobby, enPartida, finalizada }
class ResultadoOperacionSala {
final bool exitoso;
final String? codigo;
final String? mensaje;
const ResultadoOperacionSala._({
required this.exitoso,
this.codigo,
this.mensaje,
});
const ResultadoOperacionSala.ok([String? mensaje])
: this._(exitoso: true, mensaje: mensaje);
const ResultadoOperacionSala.error(String codigo, [String? mensaje])
: this._(exitoso: false, codigo: codigo, mensaje: mensaje);
Map<String, dynamic> toJson() => {
'exitoso': exitoso,
if (codigo != null) 'codigo': codigo,
if (mensaje != null) 'mensaje': mensaje,
};
}
class ClienteSala {
final String clientId;
final String? endpointId;
final String nombre;
final bool esHost;
final bool conectado;
const ClienteSala({
required this.clientId,
this.endpointId,
required this.nombre,
this.esHost = false,
this.conectado = true,
});
ClienteSala copiar({
String? clientId,
String? endpointId,
String? nombre,
bool? esHost,
bool? conectado,
}) {
return ClienteSala(
clientId: clientId ?? this.clientId,
endpointId: endpointId ?? this.endpointId,
nombre: nombre ?? this.nombre,
esHost: esHost ?? this.esHost,
conectado: conectado ?? this.conectado,
);
}
Map<String, dynamic> toJson() => {
'clientId': clientId,
if (endpointId != null) 'endpointId': endpointId,
'nombre': nombre,
'esHost': esHost,
'conectado': conectado,
};
factory ClienteSala.fromJson(Map<String, dynamic> json) => ClienteSala(
clientId: json['clientId'] as String,
endpointId: json['endpointId'] as String?,
nombre: json['nombre'] as String,
esHost: json['esHost'] as bool? ?? false,
conectado: json['conectado'] as bool? ?? true,
);
}
class EstadoSalaMultijugador {
final String roomId;
final String nombreSala;
final String hostClientId;
FaseSalaMultijugador fase;
final Map<String, ClienteSala> clientes;
final Map<String, Usuario> usuarios;
EstadoSalaMultijugador({
required this.roomId,
required this.nombreSala,
required this.hostClientId,
this.fase = FaseSalaMultijugador.lobby,
Map<String, ClienteSala>? clientes,
Map<String, Usuario>? usuarios,
}) : clientes = clientes ?? {},
usuarios = usuarios ?? {};
factory EstadoSalaMultijugador.crear({
required String roomId,
required String nombreSala,
required String hostClientId,
required String hostNombre,
}) {
final sala = EstadoSalaMultijugador(
roomId: roomId,
nombreSala: nombreSala,
hostClientId: hostClientId,
);
sala.registrarCliente(
ClienteSala(
clientId: hostClientId,
nombre: hostNombre,
esHost: true,
),
);
return sala;
}
List<Usuario> get usuariosSeleccionados =>
usuarios.values.where((usuario) => usuario.estaSeleccionado).toList();
List<Usuario> get usuariosDisponibles =>
usuarios.values.where((usuario) => usuario.estaDisponible).toList();
int get cantidadUsuariosSeleccionados => usuariosSeleccionados.length;
List<Usuario> usuariosPorCliente(String clientId) {
return usuarios.values
.where((usuario) => usuario.clienteIdSeleccionado == clientId)
.toList();
}
ClienteSala? clientePorEndpoint(String endpointId) {
for (final cliente in clientes.values) {
if (cliente.endpointId == endpointId) return cliente;
}
return null;
}
ResultadoOperacionSala registrarCliente(ClienteSala cliente) {
clientes[cliente.clientId] = cliente;
return const ResultadoOperacionSala.ok();
}
ResultadoOperacionSala crearUsuario(Usuario usuario) {
if (fase != FaseSalaMultijugador.lobby) {
return const ResultadoOperacionSala.error('sala_cerrada');
}
if (usuarios.containsKey(usuario.id)) {
return const ResultadoOperacionSala.error('usuario_duplicado');
}
final nombreNormalizado = usuario.nombre.trim().toLowerCase();
final nombreExiste = usuarios.values.any(
(u) => u.nombre.trim().toLowerCase() == nombreNormalizado,
);
if (nombreExiste) {
return const ResultadoOperacionSala.error('nombre_duplicado');
}
usuarios[usuario.id] = usuario;
return const ResultadoOperacionSala.ok();
}
ResultadoOperacionSala seleccionarUsuario({
required String usuarioId,
required String clienteId,
}) {
if (fase != FaseSalaMultijugador.lobby) {
return const ResultadoOperacionSala.error('sala_cerrada');
}
final cliente = clientes[clienteId];
if (cliente == null || !cliente.conectado) {
return const ResultadoOperacionSala.error('cliente_no_disponible');
}
final usuario = usuarios[usuarioId];
if (usuario == null) {
return const ResultadoOperacionSala.error('usuario_no_existe');
}
if (usuario.clienteIdSeleccionado == clienteId) {
return const ResultadoOperacionSala.ok();
}
if (usuario.clienteIdSeleccionado != null) {
return const ResultadoOperacionSala.error('usuario_ya_seleccionado');
}
usuarios[usuarioId] = usuario.copiar(clienteIdSeleccionado: clienteId);
return const ResultadoOperacionSala.ok();
}
ResultadoOperacionSala liberarUsuario({
required String usuarioId,
required String solicitanteClientId,
}) {
if (fase != FaseSalaMultijugador.lobby) {
return const ResultadoOperacionSala.error('sala_cerrada');
}
final usuario = usuarios[usuarioId];
if (usuario == null) {
return const ResultadoOperacionSala.error('usuario_no_existe');
}
final solicitante = clientes[solicitanteClientId];
final puedeLiberar =
usuario.clienteIdSeleccionado == solicitanteClientId ||
(solicitante?.esHost ?? false);
if (!puedeLiberar) {
return const ResultadoOperacionSala.error('usuario_de_otro_cliente');
}
usuarios[usuarioId] = usuario.copiar(liberarSeleccion: true);
return const ResultadoOperacionSala.ok();
}
ResultadoOperacionSala eliminarUsuario({
required String usuarioId,
required String solicitanteClientId,
}) {
final solicitante = clientes[solicitanteClientId];
if (!(solicitante?.esHost ?? false)) {
return const ResultadoOperacionSala.error('solo_host');
}
final usuario = usuarios[usuarioId];
if (usuario == null) {
return const ResultadoOperacionSala.error('usuario_no_existe');
}
if (usuario.estaSeleccionado) {
return const ResultadoOperacionSala.error('usuario_seleccionado');
}
usuarios.remove(usuarioId);
return const ResultadoOperacionSala.ok();
}
void desconectarCliente(String clientId) {
final cliente = clientes[clientId];
if (cliente == null) return;
clientes[clientId] = cliente.copiar(conectado: false);
if (fase != FaseSalaMultijugador.lobby) return;
for (final entry in usuarios.entries.toList()) {
if (entry.value.clienteIdSeleccionado == clientId) {
usuarios[entry.key] = entry.value.copiar(liberarSeleccion: true);
}
}
}
ResultadoOperacionSala validarInicio() {
if (fase != FaseSalaMultijugador.lobby) {
return const ResultadoOperacionSala.error('sala_cerrada');
}
if (cantidadUsuariosSeleccionados < 3) {
return const ResultadoOperacionSala.error('faltan_jugadores');
}
if (usuariosPorCliente(hostClientId).isEmpty) {
return const ResultadoOperacionSala.error('host_sin_usuario');
}
return const ResultadoOperacionSala.ok();
}
ResultadoOperacionSala iniciarPartida() {
final validacion = validarInicio();
if (!validacion.exitoso) return validacion;
fase = FaseSalaMultijugador.enPartida;
return const ResultadoOperacionSala.ok();
}
Map<String, dynamic> toJson() => {
'roomId': roomId,
'nombreSala': nombreSala,
'hostClientId': hostClientId,
'fase': fase.name,
'clientes': clientes.values.map((cliente) => cliente.toJson()).toList(),
'usuarios': usuarios.values.map((usuario) => usuario.toJson()).toList(),
};
factory EstadoSalaMultijugador.fromJson(Map<String, dynamic> json) {
final clientes = <String, ClienteSala>{};
for (final clienteJson in json['clientes'] as List<dynamic>? ?? []) {
final cliente = ClienteSala.fromJson(
clienteJson as Map<String, dynamic>,
);
clientes[cliente.clientId] = cliente;
}
final usuarios = <String, Usuario>{};
for (final usuarioJson in json['usuarios'] as List<dynamic>? ?? []) {
final usuario = Usuario.fromJson(usuarioJson as Map<String, dynamic>);
usuarios[usuario.id] = usuario;
}
return EstadoSalaMultijugador(
roomId: json['roomId'] as String,
nombreSala: json['nombreSala'] as String,
hostClientId: json['hostClientId'] as String,
fase: FaseSalaMultijugador.values.firstWhere(
(fase) => fase.name == json['fase'],
orElse: () => FaseSalaMultijugador.lobby,
),
clientes: clientes,
usuarios: usuarios,
);
}
}