Código original adicionado

This commit is contained in:
luca0N! 2020-12-31 14:23:58 -03:00
parent ab148fb510
commit a6c372c9a9
Signed by: luca0N
GPG Key ID: 68FDED9A81B90723
10 changed files with 1317 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
config.json
node_modules/*

39
cliente.js Normal file
View File

@ -0,0 +1,39 @@
/*
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>.
*/
class Cliente {
constructor(endereço, apelido, soquete){
this.endereço = endereço;
this.apelido = apelido;
this.soquete = soquete;
this.uas = Date.now(); // Última Ação Significativa: determina o tempo em que o último evento significativo deste jogador ocorreu. Utilizado para detectar usuários inativos.
this.pontos = 0;
this.opções = [];
this.pings = [];
}
acrescentarPontos(pontos){
this.pontos += pontos;
return pontos;
}
}
module.exports = Cliente;

23
config.exemplo.json Normal file
View File

@ -0,0 +1,23 @@
{
"variáveis":{
"servidor.porta": 12501,
"sala.clientes.limite": 6,
"sala.pontos.pontuaçãoMeta": 120,
"sala.tin.limite": 6,
"sala.uas.limite": 180000,
"cliente.opções.padrão": {
"bate-papo.filtro.habilitado": true
},
"sala.opções.padrão":{
"filtro.temasAdultos.habilitado": true
},
"cliente.ping.tempoMáximo": 50000,
"cliente.uas.limite": 180000,
"misc.tempo.pings": 10000,
"misc.tempo.checarInatividade": 10000
}
}

13
filtro.json Normal file
View File

@ -0,0 +1,13 @@
[
"dnRuYw==", "dnRj", "c2Zk", "Y3Js", "cGF1", "ZmRw", "Y2FjdGU=", "azc=", "Y2FyYWk=", "a3JhaQ==",
"cGludA==", "cHVuaGV0", "YnVjZXQ=", "YnVjdA==", "Z296", "Zm9k", "ZnVk", "dmFkaQ==", "cG9ycg==", "Y2FyYWxo", "a3JhbA==", "dGV0", "Y2FjZXQ=", "cGlyb2M=",
"ZGljaw==", "ZnVjaw==", "ZnVjYw==", "Y29jaw==", "Y3Vt", "aGFuZGpvYg==", "Zm9vdGpvYg==", "ZGlsZG8=", "Y3VudA==", "Yml0Y2g=", "aG9l", "d2hvcmU=", "aG9va2Vy", "YmFsbHNhYw==", "Ym93c2Fj", "dGhpYw==",
"bmln", "ZmFn", "Y29vY2g=", "cHVzcw==", "ZnVja2lu", "Zmxlc2hsaQ==",
"dnVsdmE=", "YW5hbA==", "aGVycGVz", "YWlkcw==", "aGl2", "cGVuaXM=", "bmF6aQ==",
"dHVyYmE=", "dGVyYmE=", "cHJvc3RpdHV0", "c2V4"
]

760
index.js Normal file
View File

@ -0,0 +1,760 @@
/*
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;
const wss = new WebSocket.Server({ port: vars["servidor.porta"] });
var clientes = [],
salas = [];
console.log('Servidor Força, v0.1-beta');
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 = 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{
var salaNova = new Sala(sala, [cliente], undefined, undefined, 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.
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 = clienteEscolhido;
salaObj.estadoTermo = "AGUARDANDO_LETRA";
salaObj.jf.push(clienteEscolhido);
// 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;
}
cliente.uas = Date.now();
salaObj.uas = Date.now();
// 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
/*// A sala está aceitando apenas letras?
if (salaObj.estadoTermo === "AGUARDANDO_LETRA" && !letra){
// A sala não está esperando termos.
ws.send(JSON.stringify(new Resposta("PEDIDO_CANCELADO", "SERVIDOR", "TERMO_INVÁLIDO_TERMO_INESPERADO")));
break;
}*/
// 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;
}
let resultadoTermo = salaObj.processarTermo(resposta.extra.termo);
let lp = resultadoTermo.letrasPreenchidas;
let r = resultadoTermo.resultado;
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 "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);
// 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;
if (opção === undefined || valor === 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.
sala.clientes.splice(sala.clientes.length === 2 ?
sala.clientes.indexOf(cliente.apelido)
: sala.clientes.indexOf(cliente.apelido) - 1, 1);
// Remover o jogador da fila gerada.
for (let x = 0; x < sala.jfr.length; x++)
if (sala.jfr[x] === cliente.apelido){
sala.jfr.splice(x, 1);
break;
}
// Este jogador havia escolhido a palavra atual?
if (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.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;
for (let x = 0; x < sala.clientes.length; x++){
let e = extra;
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 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){
// Escolher outro jogador para enviar o próximo termo.
let ja = sala.gerarJogadorAleatório();
sala.jf.push(ja);
sala.termoVezDe = ja;
// É 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 o cliente.
clientes[x].soquete.send(JSON.stringify(new Resposta("EVENTO", "SERVIDOR", "CHUTADO_POR_INATIVIDADE")));
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();
}
}

3
iniciar.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
clear && node .

39
pontuação.js Normal file
View File

@ -0,0 +1,39 @@
/*
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>.
*/
class Pontos {
////////////////
// Ao jogador //
////////////////
static EVENTO_JOGADOR_LETRA_ACERTADA = 2; // qtd de pts dados a um jogador que acertou uma letra.
static EVENTO_JOGADOR_PALAVRA_ACERTADA = 4; // qtd de pts dados a um jogador que acertou uma palavra.
static EVENTO_JOGADOR_LETRA_ACERTADA_CEDO = 4; // qtd de pts dados a um jogador que acertou uma palavra cedo.
static EVENTO_JOGADOR_LETRA_ERRADA_CEDO = -4; // qtd de pts dados a um jogador que errou uma palavra cedo.
/////////////////////////////////////
// Ao jogador que enviou a palavra //
/////////////////////////////////////
static EVENTO_JOGADOR_SALA_LETRA_ACERTADA = Pontos.EVENTO_JOGADOR_LETRA_ACERTADA / 2; // qtd de pts dados a um jogador quando alguém acerta uma letra de sua palavra.
static EVENTO_JOGADOR_SALA_PALAVRA_ACERTADA = Pontos.EVENTO_JOGADOR_PALAVRA_ACERTADA / 2; // qtd de pts dados a um jogador quando alguém acerta a sua palavra.
static EVENTO_JOGADOR_SALA_LETRA_ACERTADA_CEDO = Pontos.EVENTO_JOGADOR_LETRA_ACERTADA_CEDO / 2; // qtd de pts dados a um jogador quando alguém acerta a sua palavra cedo.
}
module.exports = Pontos;

35
resposta.js Normal file
View File

@ -0,0 +1,35 @@
/*
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>.
*/
// Oct 11, 2020, 23:25 (UTC -03:00)
class Resposta {
constructor(tipo, fonte, pedido, extra){
this.tipo = tipo;
this.fonte = fonte;
this.pedido = pedido;
if (extra !== undefined)
this.extra = extra;
}
}
module.exports = Resposta;

277
sala.js Normal file
View File

@ -0,0 +1,277 @@
/*
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>.
*/
class Sala {
/**
* Objeto Sala.
* @param string sala O nome da sala.
* @param Array A lista de clientes.
* @param string O apelido do líder.
* @param string O estado da sala.
*/
constructor(sala, clientes, líder, estado, opções){
this.sala = sala;
this.clientes = clientes;
this.opções = opções;
this.uas = Date.now(); // Última Ação Significante: marca o tempo em que um último evento significante tenha acontecido. Caso este evento tenha acontecido muito tempo atrás, esta sala será apagada.
this.líder = líder === undefined ? clientes[0].apelido : líder;
this.estado = estado === undefined ? "AGUARDANDO_JOGADORES" : estado;
this.pontuaçãoMeta = 0;
this.tin = 0; // tentativas incorretas
this.jfr = []; // fila de jogadores para rodadas
this.banidos = [];
}
/**
* Inicia uma nova partida nesta sala.
* @returns null
* @since 19 de outubro de 2020.
*/
novaPartida(){
this.estado = 'EM_PARTIDA';
// Popular a fila JFR.
let tmpJfr = []; // JFR temporária, utilizado para popular a JFR verdadeira.
for (let x = 0; x < this.clientes.length; x++)
tmpJfr.push(this.clientes[x].apelido);
for (let x = tmpJfr.length; x > 0; x--){
// Pegar um jogador aleatório da fila.
let num = Sala.gerarNúmeroAleatório(tmpJfr.length);
this.jfr.push(tmpJfr[num]);
tmpJfr.splice(num, 1);
}
this.novaRodada();
}
/**
* Inicia uma nova rodada nesta sala, escolhendo um jogador aleatório.
* @returns null
* @since 05 de novembro de 2020.
*/
novaRodada(){
this.lei = []; // Letras escolhidas inexistentes
this.lee = []; // Letras escolhidas existentes
this.ldc = []; // Letras descobertas
this.jpa = null; // Jogador que escolheu a palavra atual (Jogador Palavra Atual)
this.palavra = null;
this.jf = [];
this.tin = 0;
// Vamos escolher um jogador aleatório.
if (this.jfr.indexOf(this.vezDe) === this.jfr.length - 1)
// Fim da fila JFR atingido. Voltar para o começo.
this.vezDe = null;
this.vezDe = this.vezDe === undefined || this.vezDe === null ? this.jfr[0] : this.jfr[this.jfr.indexOf(this.vezDe) + 1]; // Escolher o primeiro jogador na fila caso o limite tenha sido atingido, ou escolher o próximo jogador na fila caso contrário.
}
/**
* Calcula e define a quantidade de letras na palavra atual.
* @since 23 de outubro de 2020.
*/
definirPalavraLetrasQtd(){
let letrasQtd = 0;
for (let x = 0; x < this.palavra.length; x++)
if (this.palavra[x] !== ' ')
letrasQtd++;
this.palavraLetrasQtd = letrasQtd;
}
/**
* Deduz se é possível que um jogador descubra a palavra atual baseado na quantidade de letras descobertas da palavra.
* @returns boolean Verdadeiro caso mais de 75% da palavra tenha sido descoberto ou caso apenas falte uma letra para descobrir a palavra inteira.
* @since 23 de outubro de 2020.
*/
possívelDescoberta(){
let ld = 0;
for (let x = 0; x < this.palavra.length; x++)
if (this.ldc[x] !== undefined)
ld++;
// Pelo menos 75% da palavra deve ter sido descoberta para retornar o evento de descoberta de palavra.
// Caso a palavra seja pequena, então pelo menos uma letra restante deve estar faltando.
if(ld / this.palavraLetrasQtd >= 0.75){
return true;
} else {
// Menos de 75% da palavra foi descoberta. Caso apenas uma letra esteja faltando, então retornar verdadeiro de qualquer maneira.
return this.palavraLetrasQtd - 1 === ld;
}
}
/**
* Retorna uma lista contendo as posições dos espaços na palavra atual.
* @param String busca O alvo para procurar na palavra atual.
* @returns Array Lista contendo as posições dos espaços na palavra atual.
* @since 22 de outubro de 2020.
*/
procurar(busca){
let espaços = [];
for (var x = 0; x < this.palavra.length; x++){
if (this.palavra[x] === busca)
espaços.push(x);
}
return espaços;
}
/**
* Esta função processa o termo enviado por um jogador.
* @param string termo O termo enviado pelo jogador escolhido.
* @returns "LETRA_INEXISTENTE" caso a letra inserida não exista na palavra atual; "LETRAS_PREENCHIDAS" caso a letra inserida apareça na palavra atual.
* @since 19 de outubro de 2020.
*/
processarTermo(termo){
termo = termo.toUpperCase();
let resultadoTermo = { letrasPreenchidas: 0, resultado: null };
if (termo.length === 1){
// A palavra escolhida possui a letra enviada?
if (this.palavra.indexOf(termo) === -1)
resultadoTermo.resultado = 'LETRA_INEXISTENTE';
else {
resultadoTermo.letrasPreenchidas = this.preencherCampos(termo);
resultadoTermo.resultado = 'LETRAS_PREENCHIDAS';
}
return resultadoTermo;
} else {
let palavraCorreta = this.palavra === termo;
resultadoTermo.resultado = palavraCorreta ? "PALAVRA_CORRETA" : "PALAVRA_INCORRETA";
if (palavraCorreta){
for(var x = 0; x < this.palavra.length; x++)
if (this.ldc[x] === undefined)
resultadoTermo.letrasPreenchidas++;
}
}
return resultadoTermo;
}
/**
* Gera um jogador aleatório. Esta função é utilizada para escolher a vez de alguma pessoa aleatoriamente sem ecolher pessoas que foram escolhidas.
* @returns string O apelido do jogador escolhido.
* @since 19 de outubro de 2019.
*/
gerarJogadorAleatório(){
// Se todos os jogadores já tiverem sido escolhidos, então redefinir a fila de jogadores.
if (this.jf.length === this.clientes.length - 1)
this.jf = [];
var clientesPossíveis = [];
for (var x = 0; x < this.clientes.length; x++){
var ca = this.clientes[x]; // Cliente atual
if (ca.apelido !== this.jpa // A pessoa que escolheu a palavra não pode ser escolhida.
&& this.jf.indexOf(ca.apelido) === -1) { // Jogadores que já foram escolhidos não podem ser escolhidos novamente.
clientesPossíveis.push(ca.apelido);
}
}
var je = clientesPossíveis[Sala.gerarNúmeroAleatório(clientesPossíveis.length)]; // Jogador escolhido
return je;
}
/**
* Gera um jogador aleatório para uma nova rodada.
* @returns String O apelido do jogador escolhido.
* @since 25 de dezembro de 2020.
*/
gerarJogadorAleatórioRodada(){
// Se todos os jogadores já tiverem sido escolhidos, então redefinir a fila de jogadores.
if (this.jfr.length === this.clientes.length - 1)
this.jfr = [];
let clientesPossíveis = [];
for (let x = 0; x < this.clientes.length; x++){
let ca = this.clientes[x]; // Cliente atual
if (this.jfr.indexOf(ca.apelido) === -1) // Jogadores que já foram escolhidos não podem ser escolhidos novamente.
clientesPossíveis.push(ca.apelido);
}
let je = clientesPossíveis[Sala.gerarNúmeroAleatório(clientesPossíveis.length)]; // Jogador escolhido
return je;
}
/**
* Preenche os campos com a letra especificada.
* @param string letra A letra desejada.
* @since 19 de outubro de 2020.
*/
preencherCampos(letra){
let qtd = 0; // quantidade de campos preenchidos
for (let x = 0; x < this.palavra.length; x++){
if (this.palavra[x] === letra){
this.ldc[x] = letra;
qtd++;
}
}
return qtd;
}
/**
* Retorna o vencedor da partida atual caso exista.
* @returns Object O cliente do jogador que venceu a partida ou nulo caso não um vencedor.
* @since 23 de dezembro de 2020.
*/
receberVencedor(){
let pv = []; // Lista de possíveis vencedores. Adicionar jogadores que ultrapassaram a meta aqui. Caso mais de um cliente seja adicionado aqui, então conceder vitória ao jogador que tiver a maior quantidade de pontos ou retornar nulo caso houve um empate.
for (let x = 0; x < this.clientes.length; x++)
if (this.clientes[x].pontos >= this.pontuaçãoMeta)
pv.push(this.clientes[x]);
if (pv.length === 1)
return pv[0];
else if (pv.length === 0)
return null;
else {
pv.sort(function (a, b){
return b.pontos - a.pontos;
});
// Houve um empate?
return pv[0].pontos === pv[1].pontos ? null // Houve um empate; retornar nulo.
: pv[0]; // Não houve um empate. Retornar o cliente que ficou no topo.
}
}
/**
* Checa se o endereço dado está banido nesta sala.
* @param String endereço O endereço remoto do cliente.
* @returns Verdadeiro caso o endereço esteja banido nesta sala, falso caso contrário.
* @since 24 de dezembro de 2020.
*/
banido(endereço){
for (let x = 0; x < this.banidos.length; x++)
if (this.banidos[x] === endereço)
return true;
return false;
}
/**
* Gera um número aleatório baseado no argumento providenciado.
* @param int máximo O número máximo desejado.
* @since 15 de outubro de 2020.
*/
static gerarNúmeroAleatório(máximo){
var rnd = Math.random();
var num = Math.floor(rnd * máximo);
return num;
}
}
module.exports = Sala;

125
util.js Normal file
View File

@ -0,0 +1,125 @@
/*
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>.
*/
class Util {
/**
* 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).
*/
static alertarClientesEvento(evento, salaNome, extra){
// Pesquisar pela sala desejada.
//var sala = Util.pesquisarSala(salaNome);
// Cancelar alerta caso a sala não seja existente.
if (!(sala instanceof Object))
throw new Error('Sala deve ser um objeto');
for (let x = 0; x < sala.clientes.length; x++){
let mensagem = '<' + extra.apelido + '> ' + extra.mensagem;
sala.clientes[x].soquete.send(JSON.stringify(new Resposta("EVENTO", "SERVIDOR", evento.substring(7), extra)));
}
}
/**
* 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).
*/
static pesquisarSala(sala){
for (let x = 0; x < salas.length; x++)
if (salas[x].sala === sala)
return salas[x];
}
/**
* Retorna o objeto de um cliente baseado em seu apelido e sala.
* @param Object sala O objeto da sala.
* @param String apelido O apelido dado pelo jogador.
* @since 23 de dezembro de 2020.
*/
static receberClientePorApelido(sala, apelido){
for (let x = 0; x < sala.clientes.length; x++)
if (sala.clientes[x].apelido === apelido)
return sala.clientes[x];
}
/**
* Filtra a mensagem recebida.
* @param String mensagem A mensagem para filtrar.
* @param Array filtro A lista de palavras filtradas, codificadas em Base64.
* @since 25 de dezembro de 2020.
*/
static filtrarMensagem(mensagem, filtro){
for (let x = 0; x < filtro.length; x++){
let filtragem;
let dec = Buffer.from(filtro[x], 'base64').toString(); // Termo Base64 decodificado
let regex = Util.compilarRegex(dec);
mensagem = mensagem.replace(regex, "[CENSURADO]")
}
return mensagem;
}
/**
* Transforma o padrão providenciado em um RegEx avançado.
* @param String regex O RegEx básico.
* @param String flags As opções para serem passadas para o objeto RegExp. Este parâmetro não é obrigatório, e, quando não é especificado, o padrão é "gi".
* @returns RegExp Um novo objeto RegExp criado baseado no padrão dado.
* @since 28 de dezembro de 2020.
*/
static compilarRegex(regex, flags){
if (flags === undefined) // caso nenhuma opção tenha sido dada
flags = "gi";
let subst = { // possíveis substituições.
"a": "(a|â|ã|á|à|ä|4|@)",
"i": "(i|î|ĩ|í|ì|ï|1|!|l)",
"e": "(e|ê|ẽ|é|è|ë|3)",
"s": "(s|ŝ|ś|5)",
"b": "(b|6|8)",
"t": "(t|ẗ|7)",
"o": "(o|ô|õ|ó|ò|ö|0)",
"u": "(u|û|ũ|ú|ù|ü|v)",
"c": "(c|ĉ|ć|k)"
}
let letras = [];
let detec = "(\\s|\\.|\\*)*"; // detector de separação de termos
for (let x = 0; x < regex.length; x++){
letras.push(regex[x]);
if (x != regex.length - 1)
letras.push(detec);
}
for (let x = 0; x < letras.length; x++)
if (letras[x][0] !== '(' && subst[letras[x]] !== undefined)
letras[x] = subst[letras[x]];
let cr = "";
for (let x = 0; x < letras.length; x ++)
cr += letras[x];
return new RegExp(cr, flags);
}
}
module.exports = Util;