diff --git a/client.js b/client.js
new file mode 100644
index 0000000..2f09b8e
--- /dev/null
+++ b/client.js
@@ -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 .
+
+ Entre em contato comigo por e-mail via .
+*/
+
+class Client {
+ constructor(address, nickname, socket){
+ this.address = address;
+ this.nickname = nickname;
+ this.socket = socket;
+ 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.score = 0;
+
+ this.options = [];
+ this.pings = [];
+ }
+ addScore(points){
+ this.score += points;
+ return points;
+ }
+}
+
+module.exports = Client;
+
diff --git a/query.js b/query.js
new file mode 100644
index 0000000..df38091
--- /dev/null
+++ b/query.js
@@ -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 .
+
+ Entre em contato comigo por e-mail via .
+*/
+
+// Oct 11, 2020
+
+class Query {
+ constructor(type, source, request, extra){
+ this.type = type;
+ this.source = source;
+ this.request = request;
+
+ if (extra !== undefined)
+ this.extra = extra;
+ }
+}
+
+module.exports = Query;
+
diff --git a/room.js b/room.js
new file mode 100644
index 0000000..7582151
--- /dev/null
+++ b/room.js
@@ -0,0 +1,313 @@
+/*
+ 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 .
+
+ Entre em contato comigo por e-mail via .
+*/
+
+class Room {
+ /**
+ * Room object.
+ * @param string sala The room name.
+ * @param Array An array which contains all players connected to this room.
+ * @param string The leader nickname.
+ * @param string The room status.
+ */
+ 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.
+ * @since 19 de outubro de 2020.
+ */
+ novaPartida(){
+ this.estado = 'EM_PARTIDA';
+ // Popular a fila JFR.
+ this.jf = [];
+
+ let tmpJf = [];
+ for (let x = 0; x < this.clientes.length; x++)
+ if (this.clientes[x].apelido !== this.vezDe)
+ tmpJf.push(this.clientes[x].apelido);
+
+ for (let x = tmpJf.length; x > 0; x--){
+ // Pegar um jogador aleatório da fila.
+ let num = Room.gerarNúmeroAleatório(tmpJf.length);
+ this.jf.push(tmpJf[num]);
+ tmpJf.splice(num, 1);
+ }
+
+ 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 = Room.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.pei = []; // Palavras escolhidas inexistentes
+ this.ldc = []; // Letras descobertas
+ this.jpa = null; // Jogador que escolheu a palavra atual (Jogador Palavra Atual)
+ this.palavra = null;
+ 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.
+ }
+
+ /**
+ * Retorna o próximo item da fila JF.
+ * @returns String O próximo item da fila JF.
+ * @since 02/01/2020
+ */
+ próxItemJf(){
+ let pv1 = this.jf[this.jf.indexOf(this.termoVezDe) + 1];
+ if (pv1 !== undefined)
+ return pv1;
+ return this.jf[0];
+ }
+
+ /**
+ * 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){
+ // Este termo já foi enviado?
+ if (this.lee.indexOf(termo) !== -1
+ || this.lei.indexOf(termo) !== -1)
+ resultadoTermo.resultado = "LETTER_ALREADY_CHOSEN";
+ // A palavra escolhida possui a letra enviada?
+ else if (this.palavra.indexOf(termo) === -1){
+ this.lei.push(termo);
+ resultadoTermo.resultado = "LETTER_NOT_PRESENT";
+ } else {
+ resultadoTermo.letrasPreenchidas = this.preencherCampos(termo);
+ this.lee.push(termo);
+ resultadoTermo.resultado = "LETTER_PRESENT";
+ }
+ return resultadoTermo;
+ } else {
+ // Esta palavra já foi enviada?
+ if (this.pei.indexOf(termo) !== -1){
+ resultadoTermo.resultado = "TERM_ALREADY_CHOSEN";
+ return resultadoTermo;
+ }
+ this.pei.push(termo);
+ let palavraCorreta = this.palavra === termo;
+ resultadoTermo.resultado = palavraCorreta ? "TERM_GUESSED" : "TERM_NOT_GUESSED";
+ 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 já foram escolhidas.
+ * @returns string O apelido do jogador escolhido.
+ * @since 19 de outubro de 2020.
+ */
+ 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[Room.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[Room.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 há 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 = Room;