837 lines
32 KiB
JavaScript
837 lines
32 KiB
JavaScript
/*
|
|
Força: um (livre) clone do jogo Forca
|
|
Copyright (C) 2020 luca0N!
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
Entre em contato comigo por e-mail via <luca0n@luca0n.com>.
|
|
*/
|
|
|
|
const WebSocket = require('ws');
|
|
const fs = require('fs');
|
|
|
|
const Resposta = require('./resposta.js');
|
|
const Cliente = require('./cliente.js');
|
|
const Sala = require('./sala.js');
|
|
const Util = require('./util.js');
|
|
|
|
const Pontos = require('./pontuação.js');
|
|
|
|
var vars = JSON.parse(fs.readFileSync('config.json', 'UTF8')).variáveis;
|
|
|
|
var versão = "v1.0";
|
|
|
|
const wss = new WebSocket.Server({ port: vars["servidor.porta"] });
|
|
|
|
var clientes = [],
|
|
salas = [];
|
|
|
|
console.log("Servidor Força, " + versão);
|
|
|
|
var filtros = JSON.parse(fs.readFileSync('filtro.json', 'UTF8'));
|
|
|
|
var ids = {};
|
|
|
|
ids.pings = setInterval(verificarPings, vars["misc.tempo.pings"]);
|
|
ids.checarInativos = setInterval(checarInativos, vars["misc.tempo.checarInatividade"]);
|
|
|
|
console.log('Disponível.');
|
|
|
|
wss.on('connection', ws => {
|
|
//var end = ws._socket.remoteAddress;
|
|
let cliente = new Cliente(ws._socket.remoteAddress, undefined, ws);
|
|
cliente.opções = JSON.parse(JSON.stringify(vars["cliente.opções.padrão"])); // Definir opções padrão
|
|
console.log('Nova conexão! Fonte: ' + cliente.endereço);
|
|
|
|
let aguardandoEntrada = true;
|
|
|
|
ws.on('message', msg => {
|
|
console.log(' <' + cliente.endereço + '> ' + msg);
|
|
try {
|
|
resposta = JSON.parse(msg);
|
|
} catch (e){
|
|
// A resposta enviada não está no formato JSON válido.
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "RESPOSTA_CORROMPIDA" )));
|
|
return;
|
|
}
|
|
if (aguardandoEntrada){
|
|
aguardandoEntrada = false;
|
|
cliente.apelido = resposta.fonte.split('.')[1];
|
|
// Problema #2: O apelido é válido?
|
|
let apelidoRegex = /[^A-Za-z0-9_]/g;
|
|
if (
|
|
cliente.apelido.length < 3 // O apelido tem menos que três caracteres?
|
|
|| cliente.apelido.length > 16 // O apelido tem mais que 16 caracteres?
|
|
|| apelidoRegex.exec(cliente.apelido) !== null // O apelido contém algum caractere que não seja permitido?
|
|
|| Util.filtrarMensagem(cliente.apelido, filtros) != cliente.apelido // O apelido contém termos filtrados?
|
|
){
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "NOME_ILEGAL" )));
|
|
ws.terminate();
|
|
}
|
|
}
|
|
|
|
let salaObj = cliente.sala !== undefined ? pesquisarSala(cliente.sala) : undefined;
|
|
|
|
// Interpretar a mensagem do cliente.
|
|
switch (resposta.pedido){
|
|
case "ENTRAR":{
|
|
// Entrar em uma sala ou criar ela caso inexistente.
|
|
// O parâmetro extra "sala" é necessário. Caso inexistente, retornar um erro ao cliente.
|
|
let sala = resposta.extra.sala,
|
|
salaNum,
|
|
adicionarCliente = false;
|
|
|
|
if (sala === undefined){
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", undefined, { motivo: "PARÂMETRO_FALTANDO"})));
|
|
break;
|
|
}
|
|
|
|
// Problema #2: O apelido é válido?
|
|
let salaRegex = /[^a-z0-9]/g;
|
|
if (
|
|
sala.length < 3 // O nome tem menos que três caracteres?
|
|
|| sala.length > 8 // O nome tem mais que 16 caracteres?
|
|
|| salaRegex.exec(sala) !== null // O nome contém algum caractere que não seja permitido?
|
|
|| Util.filtrarMensagem(sala, filtros) != sala // O nome contém termos filtrados?
|
|
){
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "NOME_ILEGAL" )));
|
|
ws.terminate();
|
|
}
|
|
|
|
|
|
// Agora que verificamos que os parâmetros necessários foram providenciados, vamos verificar a existência da sala.
|
|
if (salaExiste(sala)){
|
|
salaObj = pesquisarSala(sala);
|
|
// Encontrar a sala existente na lista de salas.
|
|
for (var x = 0; x < salas.length; x++){
|
|
if (salas[x].sala === sala){
|
|
//salas[x].clientes.push(cliente);
|
|
salaNum = x;
|
|
adicionarCliente = true;
|
|
salaObj = salas[x];
|
|
break;
|
|
}
|
|
}
|
|
// O IP do cliente está banido?
|
|
if (salaObj.banido(cliente.soquete.remoteAddress)){
|
|
ws.send(JSON.stringify(new Resposta("FATAL", "SERVIDOR", "BANIDO")));
|
|
return;
|
|
}
|
|
// A sala está em partida?
|
|
if (salaObj.estado === "EM_PARTIDA"){
|
|
ws.send(JSON.stringify(new Resposta("FATAL", "SERVIDOR", "SALA_EM_PARTIDA")));
|
|
return;
|
|
}
|
|
// A sala está cheia?
|
|
let limiteJogadores = vars["sala.clientes.limite"];
|
|
if (salaObj.clientes.length >= limiteJogadores){
|
|
// A sala está cheia. Retornar um código de erro ao jogador.
|
|
ws.send(JSON.stringify(new Resposta("FATAL", "SERVIDOR", "SALA_CHEIA")));
|
|
return;
|
|
}
|
|
|
|
// Gerar uma lista com os apelidos dos clientes conectados.
|
|
let jogadores = [];
|
|
for (let x = 0; x < salaObj.clientes.length; x++){
|
|
let jogadorApelido = salaObj.clientes[x].apelido;
|
|
// Verificar se já existe um usuário com o mesmo apelido na sala.
|
|
if (jogadorApelido === cliente.apelido){
|
|
// Alertar o cliente.
|
|
ws.send(JSON.stringify(new Resposta("FATAL", "SERVIDOR", "APELIDO_EXISTENTE")));
|
|
// Fechar a conexão.
|
|
ws.terminate();
|
|
return;
|
|
}
|
|
|
|
jogadores.push(jogadorApelido);
|
|
}
|
|
|
|
// Retornar um código de sucesso ao cliente, incluindo a lista de jogadores conectados, estado e o líder da sala.
|
|
ws.send(JSON.stringify(new Resposta("SUCESSO", "SERVIDOR", "SALA_EXISTENTE", { opções: salaObj.opções, líder: salaObj.líder, estado: salaObj.estado, jogadores: jogadores })));
|
|
} else {
|
|
// A sala não existe. Vamos criar ela e adicionar o usuário nela.
|
|
// salas.push(new Sala(sala, [cliente]));
|
|
// Retornar um código de sucesso ao cliente.
|
|
ws.send(JSON.stringify(new Resposta("SUCESSO", "SERVIDOR", "SALA_CRIADA")));
|
|
}
|
|
|
|
cliente.sala = sala;
|
|
clientes.push(cliente);
|
|
if (salaObj !== undefined){
|
|
salas[salaNum].clientes.push(cliente);
|
|
salaObj = salas[salaNum];
|
|
} else{
|
|
let salaNova = new Sala(sala, [cliente], undefined, undefined, JSON.parse(JSON.stringify(vars["sala.opções.padrão"])));
|
|
|
|
salaNova.pontuaçãoMeta = vars['sala.pontos.pontuaçãoMeta'];
|
|
salas.push(salaNova);
|
|
salaObj = salaNova;
|
|
}
|
|
console.log(' [' + cliente.endereço + '] Conexão estabelecida.');
|
|
// Alertar todos os jogadores sobre a entrada do jogador.
|
|
alertarClientesEvento('EVENTO_JOGADOR_ENTROU', sala, { apelido:cliente.apelido });
|
|
if (salaObj !== undefined)
|
|
// A sala possui a quantidade suficiente de jogadores para iniciar?
|
|
verificarPossibilidadeDePartida(salaObj);
|
|
break;
|
|
}
|
|
case "ENVIAR_MENSAGEM":{
|
|
// Verificar se a mensagem desejada foi providenciada pelo cliente.
|
|
if (resposta.extra.mensagem === undefined){
|
|
// A mensagem não foi providenciada pelo cliente. Retornar uma resposta de erro para o cliente.
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "PARAMETRO_FALTANDO")));
|
|
return;
|
|
}
|
|
// Verificar se a mensagem excede o limite de caracteres proposto pelo cliente.
|
|
if (resposta.extra.mensagem.length > 128) {
|
|
// Cancelar este pedido e retornar uma mensagem de erro para o cliente.
|
|
ws.send(JSON.stringify(new Resposta("PEDIDO_CANCELADO", "SERVIDOR", "MENSAGEM_MUITO_LONGA")));
|
|
return;
|
|
}
|
|
cliente.uas = Date.now();
|
|
|
|
var mensagem = '<' + cliente.apelido + '> ' + resposta.extra.mensagem;
|
|
// Enviar a mensagem para todos os clientes.
|
|
alertarClientesEvento('EVENTO_JOGADOR_MENSAGEM', cliente.sala, { apelido: cliente.apelido, mensagem: mensagem });
|
|
break;
|
|
}
|
|
case "INICIAR_PARTIDA":{
|
|
// O cliente que está tentando iniciar a partida é do líder?
|
|
if (salaObj.líder !== cliente.apelido){
|
|
// Este cliente não é do líder da sala. Retornar um erro ao cliente e cancelar a tarefa.
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "SEM_PERMISSÃO")));
|
|
break;
|
|
}
|
|
// A sala já está em partida?
|
|
if (salaObj.estado === "EM_PARTIDA"){
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "SALA_EM_PARTIDA")));
|
|
break;
|
|
}
|
|
|
|
cliente.uas = Date.now();
|
|
salaObj.uas = Date.now();
|
|
|
|
// Agora que verificamos que o cliente é do líder da sala, vamos enviar a todos os usuários um evento alertando sobre o início da partida.
|
|
|
|
salaObj.novaPartida();
|
|
|
|
alertarClientesEvento("EVENTO_SALA_ESTADO_ATUALIZADO", cliente.sala, { estado: salaObj.estado });
|
|
|
|
// E vamos alertar todos os clientes sobre o jogador escolhido.
|
|
alertarClientesEvento("EVENTO_JOGADOR_ESCOLHIDO", cliente.sala, { jogador: salaObj.vezDe });
|
|
break;
|
|
}
|
|
case "ESCOLHER_PALAVRA":{
|
|
// A sala está em partida?
|
|
if (salaObj.estado !== "EM_PARTIDA"){
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "SALA_ESTADO_NÃO_EM_PARTIDA")));
|
|
break;
|
|
}
|
|
|
|
// Verificar se é realmente a vez do cliente que enviou este pedido.
|
|
if (salaObj.vezDe !== cliente.apelido){
|
|
// Não é a vez deste cliente!
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "SEM_PERMISSÃO")));
|
|
break;
|
|
}
|
|
|
|
// Certo, é a vez deste cliente.
|
|
|
|
// A palavra enviada está vazia?
|
|
if (resposta.extra.palavra.replace(/ /g, "") === ""){
|
|
ws.send(JSON.stringify(new Resposta("PEDIDO_CANCELADO", "SERVIDOR", "PALAVRA_OU_TEMA_PROIBIDO")));
|
|
return;
|
|
}
|
|
|
|
// Verificar tamanho da palavra.
|
|
if (resposta.extra.palavra.length < vars["palavra.tamanho.mín"]
|
|
|| resposta.extra.palavra.length > vars["palavra.tamanho.máx"]){
|
|
ws.send(JSON.stringify(new Resposta("PEDIDO_CANCELADO", "SERVIDOR", "TERMO_INVÁLIDO", { erroDescrição: "PALAVRA_TAMANHO_INVÁLIDO" })));
|
|
return;
|
|
}
|
|
|
|
// Verificar tamanho do tema.
|
|
if (resposta.extra.tema.length < vars["tema.tamanho.mín"]
|
|
|| resposta.extra.tema.length > vars["tema.tamanho.máx"]){
|
|
ws.send(JSON.stringify(new Resposta("PEDIDO_CANCELADO", "SERVIDOR", "PALAVRA_OU_TEMA_PROIBIDO", { erroDescrição: "TEMA_TAMANHO_INVÁLIDO" })));
|
|
return;
|
|
}
|
|
|
|
// Verificar palavra via expressão regular.
|
|
let palavraRegexIlegal = new RegExp(vars["palavra.regex.caracteresIlegais"], 'g');
|
|
if (palavraRegexIlegal.test(resposta.extra.palavra)){
|
|
ws.send(JSON.stringify(new Resposta("PEDIDO_CANCELADO", "SERVIDOR", "PALAVRA_OU_TEMA_PROIBIDO", { erroDescrição: "PALAVRA_CARACTERES_ILEGAIS" })));
|
|
return;
|
|
}
|
|
|
|
cliente.uas = Date.now();
|
|
salaObj.uas = Date.now();
|
|
// A sala está com o filtro de temas adultos habilitado?
|
|
if (salaObj.opções["filtro.temasAdultos.habilitado"]){
|
|
// Verificar se esta palavra está filtrada.
|
|
let palavraFiltrada = Util.filtrarMensagem(resposta.extra.palavra, filtros);
|
|
let temaFiltrado = Util.filtrarMensagem(resposta.extra.tema, filtros);
|
|
// Caso há variação da palavra/tema, então a palavra/tema escolhido contém um ou mais termos impróprios.
|
|
if (palavraFiltrada !== resposta.extra.palavra
|
|
|| temaFiltrado !== resposta.extra.tema){
|
|
ws.send(JSON.stringify(new Resposta("PEDIDO_CANCELADO", "SERVIDOR", "PALAVRA_OU_TEMA_PROIBIDO")));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Redefinir tin (contagem de tentativas incorretas).
|
|
alterarTin(salaObj, 0);
|
|
|
|
salaObj.jpa = cliente.apelido;
|
|
|
|
// Escolher um jogador aleatório para escolher a primeira letra.
|
|
var clienteEscolhido = salaObj.gerarJogadorAleatório();
|
|
|
|
salaObj.palavra = resposta.extra.palavra.toUpperCase();
|
|
salaObj.definirPalavraLetrasQtd();
|
|
salaObj.termoVezDe = salaObj.próxItemJf === undefined ? salaObj.jf[0] : salaObj.próxItemJf();
|
|
|
|
if (salaObj.termoVezDe === salaObj.vezDe)
|
|
salaObj.termoVezDe = salaObj.próxItemJf();
|
|
|
|
salaObj.estadoTermo = "AGUARDANDO_LETRA";
|
|
|
|
// Vamos alertar todos os clientes sobre este evento.
|
|
alertarClientesEvento("EVENTO_PALAVRA_ESCOLHIDA", cliente.sala, { palavra: { tamanho: resposta.extra.palavra.length, espaços: salaObj.procurar(' '), hífens: salaObj.procurar('-') }, tema: resposta.extra.tema, termoVezDe: salaObj.termoVezDe });
|
|
break;
|
|
}
|
|
case "ENVIAR_TERMO":{
|
|
// A sala está em partida?
|
|
if (salaObj.estado !== "EM_PARTIDA"){
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "SALA_ESTADO_NÃO_EM_PARTIDA")));
|
|
break;
|
|
}
|
|
|
|
// Verificar se é a vez do cliente que enviou este pedido.
|
|
if (salaObj.termoVezDe !== cliente.apelido){
|
|
ws.send(JSON.stringify(new Resposta("PEDIDO_CANCELADO", "SERVIDOR", "AGUARDE_SUA_VEZ")));
|
|
break;
|
|
}
|
|
|
|
// Verificar termo via expressão regular.
|
|
let palavraRegexIlegal = new RegExp(vars["palavra.regex.caracteresIlegais"], 'g');
|
|
if (palavraRegexIlegal.test(resposta.extra.termo)){
|
|
ws.send(JSON.stringify(new Resposta("PEDIDO_CANCELADO", "SERVIDOR", "TERMO_INVÁLIDO", { erroDescrição: "TERMO_CARACTERES_ILEGAIS" })));
|
|
break;
|
|
}
|
|
|
|
// Verificar se o termo enviado possui termos filtrados CASO a sala tinha o filtro habilitado
|
|
if (salaObj.opções["filtro.temasAdultos.habilitado"] && Util.filtrarMensagem(resposta.extra.termo, filtros) !== resposta.extra.termo){
|
|
ws.send(JSON.stringify(new Resposta("PEDIDO_CANCELADO", "SERVIDOR", "TERMO_INVÁLIDO", { erroDescrição: "TERMO_IMPRÓPRIO" })));
|
|
break;
|
|
}
|
|
|
|
// Verificar o tipo do termo enviado.
|
|
let letra = resposta.extra.termo.length === 1;
|
|
let salaClientes = salaObj.clientes;
|
|
let e = Util.receberClientePorApelido(salaObj, salaObj.vezDe); // escolhedor
|
|
|
|
// O termo está vazio?
|
|
if (resposta.extra.termo.replace(/ /g, "") === ""){
|
|
ws.send(JSON.stringify(new Resposta("PEDIDO_CANCELADO", "SERVIDOR", "TERMO_INVÁLIDO", { erroDescrição: "TERMO_VAZIO" })));
|
|
return;
|
|
}
|
|
|
|
// O jogador deseja pular?
|
|
if (salaObj.estadoTermo === "AGUARDANDO_TERMO" &&
|
|
resposta.extra.termo === '?'){
|
|
alertarClientesEvento("EVENTO_JOGADOR_PULOU_TERMO", cliente.sala, { jogador: cliente.apelido });
|
|
// Escolher outro jogador.
|
|
escolherJogadorTermo(salaObj);
|
|
return;
|
|
}
|
|
|
|
// A sala está aceitando apenas termos?
|
|
if (salaObj.estadoTermo !== "AGUARDANDO_LETRA" && letra){
|
|
// A sala não está esperando letras.
|
|
ws.send(JSON.stringify(new Resposta("PEDIDO_CANCELADO", "SERVIDOR", "TERMO_INVÁLIDO_LETRA_INESPERADA")));
|
|
break;
|
|
}
|
|
|
|
cliente.uas = Date.now();
|
|
salaObj.uas = Date.now();
|
|
|
|
resposta.extra.termo = resposta.extra.termo.toUpperCase();
|
|
|
|
let resultadoTermo = salaObj.processarTermo(resposta.extra.termo);
|
|
let lp = resultadoTermo.letrasPreenchidas;
|
|
let r = resultadoTermo.resultado;
|
|
|
|
let tentarNovamente = false;
|
|
|
|
let dif = 0, // diferença da nova pontuação do jogador que enviou o termo
|
|
dife = 0; // diferença da nova pontuação do jogador que enviou a palavra atual
|
|
switch (r){
|
|
case "LETRA_JÁ_ESCOLHIDA":{
|
|
alertarClientesEvento("EVENTO_TERMO_ESCOLHIDO", cliente.sala, { tipo: letra ? "LETRA" : "TERMO", termo: resposta.extra.termo, resultado: r });
|
|
tentarNovamente = true;
|
|
break;
|
|
}
|
|
case "PALAVRA_JÁ_ESCOLHIDA":{
|
|
alertarClientesEvento("EVENTO_TERMO_ESCOLHIDO", cliente.sala, { tipo: letra ? "LETRA" : "TERMO", termo: resposta.extra.termo, resultado: r });
|
|
tentarNovamente = true;
|
|
break;
|
|
}
|
|
case "LETRAS_PREENCHIDAS":{
|
|
alertarClientesEvento("EVENTO_TERMO_ESCOLHIDO", cliente.sala, { tipo: letra ? "LETRA" : "TERMO", termo: resposta.extra.termo, resultado: r, ldc: salaObj.ldc });
|
|
|
|
// Também acrescentar pontuação ao jogador.
|
|
for (let x = 0; x < lp; x++){
|
|
dif += cliente.acrescentarPontos(Pontos.EVENTO_JOGADOR_LETRA_ACERTADA);
|
|
dife += e.acrescentarPontos(Pontos.EVENTO_JOGADOR_SALA_LETRA_ACERTADA);
|
|
}
|
|
// Alertar os clientes da nova pontuação deste jogador.
|
|
alertarClientesEvento("EVENTO_JOGADOR_PONTUAÇÃO_ALTERADA", cliente.sala, { jogador: cliente.apelido, pontos: cliente.pontos, diferença: dif });
|
|
alertarClientesEvento("EVENTO_JOGADOR_PONTUAÇÃO_ALTERADA", cliente.sala, { jogador: e.apelido, pontos: e.pontos, diferença: dife });
|
|
break;
|
|
}
|
|
case "LETRA_INEXISTENTE":{
|
|
alertarClientesEvento("EVENTO_TERMO_ESCOLHIDO", cliente.sala, { tipo: letra ? "LETRA" : "TERMO", termo: resposta.extra.termo, resultado: r });
|
|
if (alterarTin(salaObj)) return;
|
|
break;
|
|
}
|
|
case "PALAVRA_CORRETA":{
|
|
alertarClientesEvento("EVENTO_TERMO_ESCOLHIDO", cliente.sala, { acertou: true, palavra: salaObj.palavra, resultado: r });
|
|
// Acrescentar pontos ao jogador por acertar a palavra.
|
|
// Pontos pelos campos que antes estavam vazios e pontos por acertar a palavra.
|
|
|
|
let extra = salaObj.estadoTermo === 'AGUARDANDO_LETRA'; // caso verdadeiro, dar pontos extras por acertar a palavra cedo
|
|
for (let x = 0; x < lp; x++){
|
|
dif += cliente.acrescentarPontos(extra ? Pontos.EVENTO_JOGADOR_LETRA_ACERTADA_CEDO : Pontos.EVENTO_JOGADOR_LETRA_ACERTADA);
|
|
dife += e.acrescentarPontos(extra ? Pontos.EVENTO_JOGADOR_SALA_LETRA_ACERTADA_CEDO : Pontos.EVENTO_JOGADOR_SALA_LETRA_ACERTADA);
|
|
}
|
|
|
|
dif += cliente.acrescentarPontos(Pontos.EVENTO_JOGADOR_PALAVRA_ACERTADA);
|
|
dife += e.acrescentarPontos(Pontos.EVENTO_JOGADOR_SALA_PALAVRA_ACERTADA);
|
|
// Alertar os clientes da nova pontuação deste jogador.
|
|
alertarClientesEvento("EVENTO_JOGADOR_PONTUAÇÃO_ALTERADA", cliente.sala, { jogador: cliente.apelido, pontos: cliente.pontos, diferença: dif });
|
|
alertarClientesEvento("EVENTO_JOGADOR_PONTUAÇÃO_ALTERADA", cliente.sala, { jogador: e.apelido, pontos: e.pontos, diferença: dife });
|
|
|
|
// Verificar se um jogador atingiu a meta de pontos.
|
|
let vencedor = salaObj.receberVencedor();
|
|
if (vencedor !== null){
|
|
// Há um vencedor; alertar os clientes na sala que houve um vencedor.
|
|
alertarClientesEvento("EVENTO_JOGADOR_VITÓRIA", cliente.sala, { jogador: vencedor.apelido });
|
|
// Encerrar esta partida.
|
|
verificarPossibilidadeDePartida(salaObj, true);
|
|
return;
|
|
}
|
|
|
|
// Escolher outro jogador para escolher a palavra.
|
|
salaObj.novaRodada();
|
|
// E vamos alertar todos os clientes sobre o jogador escolhido.
|
|
alertarClientesEvento("EVENTO_JOGADOR_ESCOLHIDO", cliente.sala, { jogador: salaObj.vezDe });
|
|
return;
|
|
}
|
|
case "PALAVRA_INCORRETA":{
|
|
alterarTin(salaObj);
|
|
// Penalizar o jogador caso ele tenha errado a palavra cedo.
|
|
if (salaObj.estadoTermo === 'AGUARDANDO_LETRA')
|
|
for (let x = 0; x < resposta.extra.termo.length; x++)
|
|
dif += cliente.acrescentarPontos(Pontos.EVENTO_JOGADOR_LETRA_ERRADA_CEDO);
|
|
alertarClientesEvento("EVENTO_JOGADOR_PONTUAÇÃO_ALTERADA", cliente.sala, { jogador: cliente.apelido, pontos: cliente.pontos, diferença: dif });
|
|
|
|
alertarClientesEvento("EVENTO_TERMO_ESCOLHIDO", cliente.sala, { acertou: false, palavra: resposta.extra.termo, resultado: r });
|
|
break;
|
|
}
|
|
}
|
|
escolherJogadorTermo(salaObj, tentarNovamente? cliente.apelido : undefined);
|
|
// Enviar um alerta para todos os clientes da sala.
|
|
// Não é necessário especificar o apelido do jogador. Os clientes já vão estar esperando uma resposta do cliente em questão.
|
|
/*alertarClientesEvento("EVENTO_TERMO_ESCOLHIDO", cliente.sala, { tipo: letra ? "LETRA" : "TERMO", termo: resposta.extra.termo });*/
|
|
break;
|
|
}
|
|
case "CHUTAR_CLIENTE":{
|
|
// Problema #60: O cliente que enviou este pedido é o líder da sala?
|
|
if (salaObj.líder !== cliente.apelido){
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "SEM_PERMISSÃO")));
|
|
return;
|
|
}
|
|
let jogador = resposta.extra.jogador;
|
|
if (jogador === undefined){
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", undefined, { motivo: "PARÂMETRO_FALTANDO"})));
|
|
break;
|
|
}
|
|
|
|
cliente.uas = Date.now();
|
|
|
|
// Encerrar conexão com o cliente alvo.
|
|
let clienteAlvo = Util.receberClientePorApelido(salaObj, jogador);
|
|
alertarClientesEvento("EVENTO_CLIENTE_CHUTADO", cliente.sala, { cliente: clienteAlvo.apelido });
|
|
|
|
// Remover o cliente da sala.
|
|
clienteAlvo.soquete.terminate();
|
|
break;
|
|
}
|
|
case "BANIR_CLIENTE":{
|
|
// Problema #60: O cliente que enviou este pedido é o líder da sala?
|
|
if (salaObj.líder !== cliente.apelido){
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "SEM_PERMISSÃO")));
|
|
return;
|
|
}
|
|
let jogador = resposta.extra.jogador;
|
|
if (jogador === undefined){
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", undefined, { motivo: "PARÂMETRO_FALTANDO" })));
|
|
break;
|
|
}
|
|
cliente.uas = Date.now();
|
|
|
|
// Encerrar conexão com o cliente alvo.
|
|
let clienteAlvo = Util.receberClientePorApelido(salaObj, jogador);
|
|
alertarClientesEvento("EVENTO_CLIENTE_BANIDO", cliente.sala, { cliente: clienteAlvo.apelido });
|
|
|
|
// Remover o cliente da sala.
|
|
clienteAlvo.soquete.terminate();
|
|
// Adicionar o cliente na lista de banidos da sala.
|
|
salaObj.banidos.push(clienteAlvo.soquete.remoteAddress);
|
|
break;
|
|
}
|
|
case "ALTERAR_OPÇÃO":{
|
|
let opção = resposta.extra.opção,
|
|
valor = resposta.extra.valor,
|
|
alvo = resposta.extra.alvo;
|
|
if (opção === undefined || valor === undefined || alvo === undefined){
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", undefined, { motivo: "PARÂMETRO_FALTANDO"})));
|
|
break;
|
|
}
|
|
cliente.uas = Date.now();
|
|
|
|
// Definir opção.
|
|
if (resposta.extra.alvo === "CLIENTE"){
|
|
cliente.opções[opção] = valor;
|
|
|
|
ws.send(JSON.stringify(new Resposta("SUCESSO", "SERVIDOR", "OPÇÃO_ALTERADA", { opção: opção, novoValor: cliente.opções[opção] })));
|
|
} else if (resposta.extra.alvo === "SALA") {
|
|
// O cliente que está enviando este pedido é o líder da sala?
|
|
if (salaObj.líder !== cliente.apelido){
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "SEM_PERMISSÃO")));
|
|
break;
|
|
}
|
|
|
|
// A sala já está em partida?
|
|
if (salaObj.estado === "EM_PARTIDA"){
|
|
ws.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "SALA_EM_PARTIDA")));
|
|
break;
|
|
}
|
|
// Agora que verificamos que este cliente é o líder, vamos alterar a configuração da sala.
|
|
salaObj.opções[opção] = valor;
|
|
ws.send(JSON.stringify(new Resposta("SUCESSO", "SERVIDOR", "OPÇÃO_ALTERADA", { alvo: resposta.extra.alvo, opção: opção, novoValor: salaObj.opções[opção] })));
|
|
// Vamos alertar os clientes a opção alterada.
|
|
alertarClientesEvento("EVENTO_SALA_OPÇÃO_ALTERADA", cliente.sala, { opção: opção, valor: valor});
|
|
}
|
|
break;
|
|
}
|
|
case "PING":{
|
|
// Adicionar tempo para a lista de pings do cliente.
|
|
cliente.pings.push(Date.now());
|
|
ws.send(JSON.stringify(new Resposta("SUCESSO", "SERVIDOR", "PONG", { tempo: resposta.extra.tempo })));
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
ws.on('error', erro => {
|
|
console.log(' [' + cliente.endereço + '] Conexão fechada devido a erro.');
|
|
// Remover o cliente da lista de clientes.
|
|
removerCliente(cliente.endereço);
|
|
});
|
|
|
|
ws.on('close', function(){
|
|
console.log(' [' + cliente.endereço + '] Conexão fechada.');
|
|
// Remover o cliente da lista de clientes.
|
|
removerCliente(cliente, 'MOTIVO_CONEXÃO_FECHADA');
|
|
});
|
|
let resposta = new Resposta("TESTE_CON", "SERVIDOR");
|
|
ws.send(JSON.stringify(resposta));
|
|
});
|
|
|
|
/**
|
|
* Remove um cliente da lista de clientes e da sala conectada.
|
|
* @param Cliente O objeto Cliente.
|
|
* @param string O motivo pelo qual o jogador está sendo removido.
|
|
* @since 12 de outubro de 2020, 12:51 (UTC -03:00).
|
|
*/
|
|
function removerCliente(cliente, motivo){
|
|
let sala = pesquisarSala(cliente.sala);
|
|
|
|
// Verifica se o cliente está em uma sala.
|
|
if (sala === undefined){
|
|
//cliente.soquete.send(JSON.stringify(new Resposta("ERRO", "SERVIDOR", "SEM_SALA")));
|
|
return -1;
|
|
}
|
|
|
|
// Remover o cliente da lista de clientes.
|
|
clientes.splice(clientes.indexOf(cliente), 1);
|
|
|
|
// Verificar se este é o único cliente conectado na sala. Se este for o caso, então apagar a sala.
|
|
if (sala.clientes.length === 1){
|
|
salas.splice(salas.indexOf(sala));
|
|
return;
|
|
}
|
|
|
|
let líder = sala.líder === cliente.apelido;
|
|
|
|
if (líder) // Caso o líder sair da sala, então apagar a sala.
|
|
alertarClientesEvento('EVENTO_LÍDER_SAIU', sala.sala, {});
|
|
|
|
// Este cliente está na sala?
|
|
//if (sala.clientes.indexOf(cliente.apelido) === -1)
|
|
//return;
|
|
|
|
// Alertar os clientes na sala sobre o jogador que está sendo removido caso um motivo tenha sido especificado.
|
|
alertarClientesEvento('EVENTO_JOGADOR_SAIU', cliente.sala, { apelido: cliente.apelido, motivo: motivo });
|
|
|
|
if(líder) // Remover todos os jogadores da sala.
|
|
for (let x = sala.clientes.length; x > 0; x--)
|
|
sala.clientes[x-1].soquete.terminate();
|
|
|
|
// Remover o cliente da lista de clientes da sala.
|
|
let índice = sala.clientes.indexOf(cliente);
|
|
sala.clientes.splice(índice, 1);
|
|
|
|
// Remover o jogador das filas geradas.
|
|
sala.jfr.splice(sala.jfr.indexOf(cliente.apelido), 1);
|
|
if (sala.jf !== undefined)
|
|
sala.jf.splice(sala.jf.indexOf(cliente.apelido), 1);
|
|
|
|
// Este jogador havia escolhido a palavra atual?
|
|
if (sala.clientes.length !== 1 && sala.vezDe === cliente.apelido){
|
|
// Escolher outro jogador.
|
|
sala.novaRodada();
|
|
// E vamos alertar todos os clientes sobre o jogador escolhido.
|
|
alertarClientesEvento("EVENTO_JOGADOR_ESCOLHIDO", cliente.sala, { jogador: sala.vezDe });
|
|
}
|
|
// Este jogador estava escolhendo um termo?
|
|
else if (sala.clientes.length !== 1 && sala.termoVezDe === cliente.apelido){
|
|
// Removê-lo da fila de jogadores.
|
|
for (let x = 0; x < sala.jf.length; x++)
|
|
if (sala.jf[x] === cliente.apelido){
|
|
sala.jf.splice(x, 1);
|
|
break;
|
|
}
|
|
// Escolher outro jogador.
|
|
escolherJogadorTermo(sala);
|
|
}
|
|
|
|
// Se a sala estiver pronta para iniciar uma nova partida, ela ainda continuará preparada após a saída deste cliente?
|
|
if (!líder) verificarPossibilidadeDePartida(sala);
|
|
}
|
|
|
|
/**
|
|
* Verifica a existência de uma sala.
|
|
* @param string sala O código da sala.
|
|
* @returns boolean Verdadeiro caso a sala seja existente.
|
|
* @since 12 de outubro de 2020, 13:15 (UTC -03:00).
|
|
*/
|
|
function salaExiste(sala){
|
|
for (var x = 0; x < salas.length; x++)
|
|
if (salas[x].sala === sala)
|
|
return true;
|
|
|
|
// Nenhuma sala foi encontrada no ciclo. Retornar falso.
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Alerta todos os clientes de uma sala sobre um evento.
|
|
* @param string O ID do evento.
|
|
* @param string salaNome A sala desejada.
|
|
* @param object extra Os dados extra do evento.
|
|
* @since 12 de outubro de 2020, 13:32 (UTC -03:00).
|
|
*/
|
|
function alertarClientesEvento(evento, salaNome, extra){
|
|
// Pesquisar pela sala desejada.
|
|
let sala = pesquisarSala(salaNome);
|
|
|
|
// Cancelar alerta caso a sala não seja existente.
|
|
if (sala === undefined)
|
|
return;
|
|
|
|
let msgFiltrada = null;
|
|
let ogMsg;
|
|
if (extra !== undefined)
|
|
ogMsg = extra.mensagem;
|
|
|
|
for (let x = 0; x < sala.clientes.length; x++){
|
|
let e = extra;
|
|
if (e !== undefined)
|
|
e.mensagem = ogMsg;
|
|
|
|
if (e !== undefined && e.mensagem !== undefined){
|
|
// Evitar problemas de formatação com colchetes.
|
|
e.mensagem = e.mensagem.replace(/\[/g, "\\[")
|
|
.replace(/\]/g, "\\]");
|
|
if (sala.clientes[x].opções["bate-papo.filtro.habilitado"]){
|
|
if (msgFiltrada === null) // Evitar de filtrar a mensagem caso ela já tenha sido filtrada.
|
|
msgFiltrada = Util.filtrarMensagem(e.mensagem, filtros);
|
|
e.mensagem = msgFiltrada;
|
|
}
|
|
}
|
|
|
|
sala.clientes[x].soquete.send(JSON.stringify(new Resposta("EVENTO", "SERVIDOR", evento.substring(7), e)));
|
|
}
|
|
}
|
|
|
|
function alertarClienteEvento(evento, salaNome, extra, cliente){
|
|
// Pesquisar pela sala desejada.
|
|
let sala = pesquisarSala(salaNome);
|
|
|
|
// Cancelar alerta caso a sala não seja existente.
|
|
if (sala === undefined)
|
|
return;
|
|
for (let x = 0; x < sala.clientes.length; x++){
|
|
if (sala.clientes[x].apelido === cliente.apelido){
|
|
let mensagem = '<' + extra.apelido + '> ' + extra.mensagem;
|
|
sala.clientes[x].soquete.send(JSON.stringify(new Resposta("EVENTO", "SERVIDOR", evento.substring(7), extra)));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pesquisa por uma sala com o nome desejado e retorna um objeto Sala correspondente.
|
|
* @param string sala O nome da sala.
|
|
* @returns Sala O objeto da sala.
|
|
* @since 12 de outubro de 2020, 19:49 (UTC -03:00).
|
|
*/
|
|
function pesquisarSala(sala){
|
|
for (var x = 0; x < salas.length; x++)
|
|
if (salas[x].sala === sala)
|
|
return salas[x];
|
|
}
|
|
|
|
/**
|
|
* Verifica se há a possibilidade de iniciar uma nova partida baseado na quantidade de clientes conectados à sala especificada. Caso exista essa possibilidade, então alterar o estado da sala e alertar todos os jogadores (incluindo o líder) sobre isso.
|
|
* @param Sala sala O objeto da sala.
|
|
* @return boolean Verdadeiro caso exista a possibilidade, falso caso contrário.
|
|
* @since 14 de outubro de 2020.
|
|
*/
|
|
function verificarPossibilidadeDePartida(sala, forçar){
|
|
if (sala.estado === "EM_PARTIDA" && sala.clientes.length > 1 && !forçar)
|
|
return true;
|
|
|
|
// Alterar o estado da sala.
|
|
sala.estado = sala.clientes.length === 1 ? "AGUARDANDO_JOGADORES" : "AGUARDANDO_LÍDER";
|
|
|
|
// Alertar os clientes.
|
|
alertarClientesEvento("EVENTO_SALA_ESTADO_ATUALIZADO", sala.sala, { estado: sala.estado });
|
|
return sala.clientes.length > 1;
|
|
}
|
|
|
|
/**
|
|
* Incrementa o tin da sala especificada e alerta a todos os clientes sobre o tin atualizado.
|
|
* @param Object sala O objeto da sala.
|
|
* @param int valor O novo valor do tin. Caso este parâmetro não esteja definido, então o tin terá seu valor incrementado por 1.
|
|
* @returns Verdadeiro caso uma nova partida tenha sido iniciada.
|
|
* @since 24 de dezembro de 2020.
|
|
*/
|
|
function alterarTin(sala, valor){
|
|
if (valor === undefined)
|
|
sala.tin++;
|
|
else
|
|
sala.tin = valor;
|
|
alertarClientesEvento("EVENTO_TIN_ATUALIZADO", sala.sala, { tin: sala.tin });
|
|
// Tin máximo atingido?
|
|
if (sala.tin >= vars['sala.tin.limite']){
|
|
alertarClientesEvento("EVENTO_TIN_MÁXIMO_ATINGIDO", sala.sala, { palavra: sala.palavra });
|
|
// Nova rodada.
|
|
// Escolher outro jogador para escolher a palavra.
|
|
sala.novaRodada();
|
|
// E vamos alertar todos os clientes sobre o jogador escolhido.
|
|
alertarClientesEvento("EVENTO_JOGADOR_ESCOLHIDO", sala.sala, { jogador: sala.vezDe });
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function escolherJogadorTermo(sala, jogador){
|
|
if (jogador !== undefined)
|
|
sala.termoVezDe = jogador;
|
|
else {
|
|
// Escolher outro jogador para enviar o próximo termo.
|
|
if (sala.jf.indexOf(sala.termoVezDe) === sala.jf.length - 1)
|
|
sala.termoVezDe = null;
|
|
|
|
sala.termoVezDe = sala.próxItemJf();
|
|
|
|
if (sala.termoVezDe === sala.vezDe)
|
|
sala.termoVezDe = sala.próxItemJf();
|
|
}
|
|
|
|
let ja = sala.termoVezDe;
|
|
|
|
// É hora de descobrir a palavra inteira?
|
|
let possívelDescoberta = sala.possívelDescoberta();
|
|
if (possívelDescoberta)
|
|
sala.estadoTermo = "AGUARDANDO_TERMO";
|
|
|
|
alertarClientesEvento("EVENTO_JOGADOR_ESCOLHIDO_TERMO", sala.sala, { jogador: ja, tipo: sala.possívelDescoberta() ? "TERMO" : "LETRA" });
|
|
}
|
|
|
|
/**
|
|
* Verifica os pings enviados pelos clientes. Caso um ping recente não tenha sido enviado pelo cliente, então a sua conexão será terminada.
|
|
* @since 30 de dezembro de 2020.
|
|
*/
|
|
function verificarPings(){
|
|
for (let x = 0; x < clientes.length; x++){
|
|
let cliente = clientes[x],
|
|
pings = cliente.pings;
|
|
if (pings.length >= 10)
|
|
pings.splice(0, 1);
|
|
|
|
let delta = Date.now() - pings[pings.length - 1],
|
|
máx = vars["cliente.ping.tempoMáximo"];
|
|
|
|
if (delta >= máx){
|
|
console.log("[" + cliente.endereço + "] O cliente não enviou pings recentemente. Fechando conexão.");
|
|
cliente.soquete.terminate();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fecha conexões e salas inativas.
|
|
* @since 30 de dezembro de 2020.
|
|
*/
|
|
function checarInativos(){
|
|
///////////////
|
|
// Jogadores //
|
|
///////////////
|
|
for (let x = 0; x < clientes.length; x++){
|
|
if (Date.now() - clientes[x].uas >= vars["cliente.uas.limite"]){
|
|
console.log("[" + clientes[x].endereço + "] O jogador parece estar inativo. Fechando conexão.");
|
|
// Alertar os clientes.
|
|
alertarClientesEvento("EVENTO_JOGADOR_CHUTADO_POR_INATIVIDADE", clientes[x].sala, { jogador: clientes[x].apelido });
|
|
clientes[x].soquete.terminate();
|
|
}
|
|
}
|
|
|
|
///////////
|
|
// Salas //
|
|
///////////
|
|
for (let x = 0; x < salas.length; x++)
|
|
if (Date.now() - salas[x].uas >= vars["sala.uas.limite"]){
|
|
console.log(salas[x].sala + ": esta sala parece estar inativa. Removendo ela.");
|
|
// Esta sala está inativa. Alertar aos clientes de que eles serão desconectados desta sala por inatividade.
|
|
alertarClientesEvento("EVENTO_SALA_INATIVA", salas[x].sala);
|
|
// Terminar conexão com o líder, que automaticamente irá desconectar todos da sala.
|
|
Util.receberClientePorApelido(salas[x], salas[x].líder).soquete.terminate();
|
|
}
|
|
}
|
|
|