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 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 toJson() => { 'clientId': clientId, if (endpointId != null) 'endpointId': endpointId, 'nombre': nombre, 'esHost': esHost, 'conectado': conectado, }; factory ClienteSala.fromJson(Map 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 clientes; final Map usuarios; EstadoSalaMultijugador({ required this.roomId, required this.nombreSala, required this.hostClientId, this.fase = FaseSalaMultijugador.lobby, Map? clientes, Map? 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 get usuariosSeleccionados => usuarios.values.where((usuario) => usuario.estaSeleccionado).toList(); List get usuariosDisponibles => usuarios.values.where((usuario) => usuario.estaDisponible).toList(); int get cantidadUsuariosSeleccionados => usuariosSeleccionados.length; List 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 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 json) { final clientes = {}; for (final clienteJson in json['clientes'] as List? ?? []) { final cliente = ClienteSala.fromJson( clienteJson as Map, ); clientes[cliente.clientId] = cliente; } final usuarios = {}; for (final usuarioJson in json['usuarios'] as List? ?? []) { final usuario = Usuario.fromJson(usuarioJson as Map); 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, ); } }