Skip to content

Commit

Permalink
Merge pull request #198 from chesterbr/completa-comando-e
Browse files Browse the repository at this point in the history
Completa implementação do comando E
  • Loading branch information
chesterbr authored Aug 5, 2023
2 parents 30f9310 + f8579dc commit ea58de1
Show file tree
Hide file tree
Showing 14 changed files with 235 additions and 41 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0-M1'
testImplementation 'org.mockito:mockito-core:5.3.1'
testImplementation 'org.mockito:mockito-junit-jupiter:5.3.1'
testImplementation 'org.hamcrest:hamcrest:2.2'
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ protected void exibeMesaForaDoJogo(String notificacaoI) {
// parte inferior da tela (textViewJogador1), sucedido por
// "(você)"; sucede a pessoa que é gerente com "(gerente)"
int p = (posJogador - 1) % 4;
textViewJogador1.setText(nomes[p] + (p == 0 ? " (você/gerente)" : "(você)"));
textViewJogador1.setText(nomes[p] + (p == 0 ? " (você/gerente)" : " (você)"));
p = (p + 1) % 4;
textViewJogador2.setText(nomes[p] + (p == 0 ? " (gerente)" : ""));
p = (p + 1) % 4;
Expand Down
1 change: 1 addition & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0-M1'
testImplementation 'org.mockito:mockito-core:5.3.1'
testImplementation 'org.mockito:mockito-junit-jupiter:5.3.1'
testImplementation 'org.hamcrest:hamcrest:2.2'
}

test {
Expand Down
15 changes: 15 additions & 0 deletions core/src/main/java/me/chester/minitruco/core/Modo.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
*/
public interface Modo {

/**
* @return instância de Modo correspondente ao modoStr
* @param modoStr String de 1 caractere indicando o modo desejado. Ex.:
* "M" para mineiro, "P" para paulista, etc.
* @throws IllegalArgumentException se o modo for inválido
*/
static Modo fromString(String modoStr) {
switch (modoStr) {
case "M":
Expand All @@ -25,6 +31,15 @@ static Modo fromString(String modoStr) {
}
}

static boolean isModoValido(String modoStr) {
try {
fromString(modoStr);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}

int pontuacaoParaMaoDeX();

int valorInicialDaMao();
Expand Down
5 changes: 3 additions & 2 deletions core/src/test/java/me/chester/minitruco/core/JogadorTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package me.chester.minitruco.core;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -38,7 +39,7 @@ private static String sanitizaNome(String nome) {

void assertNomeDefault(String nome) {
String regex = "^sem_nome_\\d{1,3}$";
assertTrue(nome.matches(regex), nome + " não deu match em " + regex);
assertThat(nome, matchesPattern(regex));
}

@Test
Expand Down
16 changes: 10 additions & 6 deletions docs/documentacao-para-desenvolvimento.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,11 @@ Quando o miniTruco foi criado (2005), poucas pessoas possuíam celulares e plano

Idealmente isso seria feito serializando as chamadas e objetos com um protocolo binário (se fosse hoje em dia, algo como [Protobuf](https://protobuf.dev/)). Mas eu também queria que fosse possível interagir diretamente com o servidor via terminal (para testes, depuração e também por diversão), então acabei criando uma "linguagem" que define comandos e notificações em texto simples.

O protocolo consiste em _comandos_ enviados pelo cliente (ex.: `J 3c` para "`J`ogar o `3` de `c`opas") e _notificações_ enviadas pelo servidor (ex.: `V 2 F` para "`v`ez do jogador na posição `2`, que não pode (`F`alse) jogar fechada). Os clientes devem processar as notificações assincronamente, e podem enviar comandos a qualquer momento, desde que faça sentido (ex.: o comando `J` só funciona se um jogo estiver em andamento e for a vez do jogador).
O protocolo consiste em _comandos_ enviados pelo cliente (ex.: `J 3c` para "`J`ogar o `3` de `c`opas") e _notificações_ enviadas pelo servidor (ex.: `V 2 F` para "`v`ez do jogador na posição `2`, que não pode (`F`alse) jogar fechada).

Os clientes devem processar as notificações assincronamente, e podem enviar comandos a qualquer momento, desde que faça sentido (ex.: o comando `J` só funciona se um jogo estiver em andamento e for a vez do jogador).

Comandos com erros de sintaxe ou argumentos inválidos são ignorados.

### Testando (jogando) via nc/telnet

Expand Down Expand Up @@ -310,7 +314,8 @@ TODO colocar um exemplo de jogo aqui (GIF ou whatnot)
- `<nomes>`: Quatro sequências de caracteres, separadas por `|`. Exemplo: `john|bot|ringo|george`.
- `<modo>`: `P` para truco paulista, `M` para truco mineiro, `V` para manilha velha ou `L` baralho limpo.
- `<jogador>`: Posição de um jogador na sala/partida, de 1 a 4. É constante durante a partida, mas pode mudar fora dela (o servidor manda uma notificação `I` sempre que a formação da sala mudar).
- `<sala>` : Informa o tipo de sala em que estamos conectados. Pode ser `BLT` (bluetooth), `PUB` (pública) ou `PRI-nnnnn` (privada, com o código nnnnn).
- `<sala>` : Informa o tipo de sala em que estamos conectados. Pode ser `BLT` (bluetooth), `PUB` (pública) ou `PRI-código` (privada, com o código especificado).
- `<codigo>` : String de números e letras maiúsculas que identifica uma sala privada. Exemplo: `A9327J`.
- `<equipe>`: Uma das duas equipes (duplas). Pode ser 1 (equpe dos jogadores 1 e 3) ou 2 (jogadores 2 e 4).
- `<frase>`: número aleatório grande que permite que todos os clientes mostrem a mesma frase (o "balãozinho") para um evento. Por exemplo, se o jogador 1 pediu truco (paulista) e o número sorteado foi 12345678, todos irão receber `T 1 3 12345678`; se o cliente tem 8 frases possíveis para truco, ele calcula 12345678 % 8 = 6 e exibe a frase de índice 6. Dessa forma, todos os clientes mostram a mesma frase (se estiverem com a mesma versão do [strings.xml](../app/src/main/res/values/strings.xml)) e o servidor não tem que saber quantas frases tem cada tipo de mensagem.

Expand All @@ -321,6 +326,9 @@ TODO colocar um exemplo de jogo aqui (GIF ou whatnot)
- `B <numero>`: Informa o número do build do cliente, para que o servidor verifique compatibilidade
- `N <nome>`: Define o nome do jogador (é sanitizado; se for 100% inválido recebe um default)
- `E PUB <modo>`: Entra em uma sala pública (criando, se não tiver nenhuma com vaga) com o modo especificado
- `E NPU <modo>`: Cria uma nova sala pública e entra nela
- `E PRI <modo> <nome>`: Cria uma nova sala privada e entra nela
- `E PRI-<codigo>`: Entra em uma sala privada com o código <codigo>

#### Dentro da sala (fora de jogo)
- `S`: Sai da sala (encerrando a partida, se houver uma em andamento)
Expand All @@ -347,10 +355,6 @@ TODO colocar um exemplo de jogo aqui (GIF ou whatnot)

- `W x.y`: `Versão do servidor (outras infos podem vir no futuro)
- `X CI`: `Comando inválido
- `X AI`: `Argumento inválido
- `X FS`: `Você não está numa sala
- `X NO`: `É preciso atribuir um nome para entrar na sala
- `X JE sala`: Você já está na sala de código `sala`
- `N nome`: Seu nome foi definido como `nome`
- `I <nomes> <modo> <jogador> <sala>`: Informações da sala (vide detalhes em "convenções")
- `P <jogador>`: Início da partida
Expand Down
1 change: 1 addition & 0 deletions server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0-M1'
testImplementation 'org.mockito:mockito-core:5.3.1'
testImplementation 'org.mockito:mockito-junit-jupiter:5.3.1'
testImplementation 'org.hamcrest:hamcrest:2.2'
}
java {
sourceCompatibility = JavaVersion.VERSION_19
Expand Down
59 changes: 37 additions & 22 deletions server/src/main/java/me/chester/minitruco/server/ComandoE.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,54 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/* Copyright © 2005-2023 Carlos Duarte do Nascimento "Chester" <cd@pobox.com> */

import me.chester.minitruco.core.Modo;

/**
* Entra numa sala.
* <p>
* Parâmetros:
* - "R ___" para entrar em uma sala (ou criar uma) com as regras especificadas
* (cada _ é T ou F para baralho limpo, manilha velha e modo mineiro)
* <p>
* Obs.: Se o jogador não tiver nome, um nome implícito será dado
* Entra numa sala. Sintaxe:
* <ul>
* <li>PUB modo - procura uma sala pública compatível, se não achar, cria</li>
* <li>NPU modo - cria nova sala pública</li>
* <li>PRI modo - cria nova sala privada</li>
* <li>PRI-nnnnn - entra numa sala privada existente</li>
* </ul>
* modos: "P" para truco paulista, "M" para mineiro, etc; vide classe `Modo`
*/
public class ComandoE extends Comando {

@Override
public void executa(String[] args, JogadorConectado j) {

try {
if (j.getNome().equals("unnamed")) {
j.println("X NO");
String subcomando, modo = null;
if (args.length < 2 || args.length > 3) {
return;
}
subcomando = args[1];
if (args.length == 3) {
modo = args[2];
if (!Modo.isModoValido(modo)) {
return;
}
if (j.getSala() != null) {
// TODO: mostrar o código se for sala pública?
j.println("X JE " + j.getSala().codigo);
}
if (j.getSala() != null) {
(new ComandoS()).executa(new String[]{"S"}, j);
}
try {
Sala s;
if ("PUB".equals(subcomando)) {
s = Sala.colocaEmSalaPublica(j, modo);
} else if ("NPU".equals(subcomando)) {
s = Sala.colocaEmNovaSala(j,true, modo);
} else if ("PRI".equals(subcomando)) {
s = Sala.colocaEmNovaSala(j,false, modo);
} else if (subcomando.startsWith("PRI-")) {
s = Sala.colocaEmSalaPrivada(j, subcomando.substring(4));
} else {
if ("PUB".equals(args[1])) {
Sala s = Sala.colocaEmSalaPublica(j, args[2]);
s.mandaInfoParaTodos();
// TODO: implementar comandos para sala privada
} else {
j.println("X AI");
}
return;
}
if (s != null) {
s.mandaInfoParaTodos();
}
} catch (NumberFormatException | IndexOutOfBoundsException e) {
j.println("X AI");
// Simplesmente ignoramos qualquer erro causado pelo comando
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public void executa(String[] args, JogadorConectado j) {
String nome = Jogador.sanitizaNome(
(comando + " ").substring(2)
);
ServerLogger.evento("Nome mudou de " + j.getNome() + " para " + nome);
j.setNome(nome);
j.println("N " + nome);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class JogadorConectado extends Jogador implements Runnable {
*/
public JogadorConectado(Socket cliente) {
this.cliente = cliente;
this.setNome(Jogador.sanitizaNome("")); // atribui um nome padrão
}

@FunctionalInterface
Expand Down
37 changes: 28 additions & 9 deletions server/src/main/java/me/chester/minitruco/server/Sala.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
public class Sala {

/**
* Salas criadas por usuários (a chave é o código da sala)
* Salas privadas (a chave é o código da sala)
*/
private static final Map<String, Sala> salasPrivadas = new HashMap<>();

Expand Down Expand Up @@ -58,7 +58,7 @@ public static void limpaSalas() {
}

/**
* Código usado para os amigos acharem a sala; null se for uma sala pública
* Código usado para os amigos acharem a sala privada; null se for uma sala pública
*/
String codigo;

Expand All @@ -76,13 +76,13 @@ public static void limpaSalas() {
private PartidaLocal partida = null;

/**
* Cria uma sala .
* Cria uma nova sala, pública ou privada
*/
public Sala(boolean publica, String modo) {
if (publica) {
salasPublicasDisponiveis.add(this);
} else {
String codigo = UUID.randomUUID().toString().substring(0, 5);
String codigo = UUID.randomUUID().toString().toUpperCase().substring(0, 5);
this.codigo = codigo;
salasPrivadas.put(codigo, this);
}
Expand All @@ -93,6 +93,7 @@ public Sala(boolean publica, String modo) {
* Coloca o jogador em uma sala pública que tenha aquele modo de partida
* criando uma caso estejam todas lotadas
*
* @return sala em que foi colocado
*/
public static synchronized Sala colocaEmSalaPublica(JogadorConectado j, String modo) {
Sala sala = salasPublicasDisponiveis.stream().filter(s ->
Expand All @@ -106,13 +107,31 @@ public static synchronized Sala colocaEmSalaPublica(JogadorConectado j, String m
}

/**
* Coloca o jogador em uma sala privada pré-existente
* @param codigo o código recebido de quem criou a sala
* @return false caso a sala não tenha sido encontrada ou esteja lotada
* Coloca o jogador em uma sala privada pré-existente (através do código)
*
* @return sala em que foi colocado, ou null se não existir/estiver lotada
*/
public static synchronized boolean colocaEmSalaPrivada(JogadorConectado j, String codigo) {
public static synchronized Sala colocaEmSalaPrivada(JogadorConectado j, String codigo) {
Sala sala = salasPrivadas.get(codigo);
return (sala != null && sala.adiciona(j));
if (sala != null && sala.adiciona(j)) {
return sala;
}
return null;
}

/**
* Cria uma nova sala e coloca o jogador nela
*
* @param j Jogador a ser colocado na sala (vai ser sempre o gerente)
* @param publica true se a sala for pública, false se for privada
* @param modo "P" para paulista, "M" para mineiro, etc.
* @return nova sala em que foi colocado
*/
public static synchronized Sala colocaEmNovaSala(JogadorConectado j, boolean publica, String modo) {
Sala sala = new Sala(publica, modo);
sala.adiciona(j);

return sala;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static synchronized void evento(Jogador j, String mensagem) {
System.out.print("[sem_sala] ");
}
if (j != null) {
System.out.print(!j.getNome().equals("unnamed") ? j.getNome() : "[sem_nome]");
System.out.print(j.getNome());
if (j instanceof JogadorConectado) {
System.out.print('@');
System.out.print(((JogadorConectado) j).getIp());
Expand Down
Loading

0 comments on commit ea58de1

Please sign in to comment.