diff --git a/app/build.gradle b/app/build.gradle index 1f23be05..7e156f63 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,6 +33,9 @@ android { } } } + buildFeatures { + buildConfig true + } } dependencies { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c3fe258f..f8f53bcb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,6 +57,7 @@ */ + +import android.content.Context; +import android.content.SharedPreferences; + +import androidx.preference.PreferenceManager; + +import me.chester.minitruco.R; + +public class PreferenceUtils { + public static String getLetraDoModo(Context context) { + return getPreferences(context).getString("modo", "P"); + } + + public static String getServidor(Context context) { + return getPreferences(context).getBoolean("servidorLocal", false) ? + context.getString(R.string.opcoes_default_servidor_local) : + context.getString(R.string.opcoes_default_servidor); + } + + private static SharedPreferences getPreferences(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context); + } +} diff --git a/app/src/main/java/me/chester/minitruco/android/TituloActivity.java b/app/src/main/java/me/chester/minitruco/android/TituloActivity.java index 189dc0e0..6ea91c0b 100644 --- a/app/src/main/java/me/chester/minitruco/android/TituloActivity.java +++ b/app/src/main/java/me/chester/minitruco/android/TituloActivity.java @@ -2,6 +2,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.provider.Settings.Global.DEVICE_NAME; +import static me.chester.minitruco.android.PreferenceUtils.getLetraDoModo; import android.app.AlertDialog; import android.bluetooth.BluetoothAdapter; @@ -24,6 +25,7 @@ import android.widget.CheckBox; import android.widget.EditText; import android.widget.TextView; +import android.widget.Toast; import androidx.core.util.Consumer; import androidx.preference.PreferenceManager; @@ -33,6 +35,7 @@ import me.chester.minitruco.android.multiplayer.bluetooth.ClienteBluetoothActivity; import me.chester.minitruco.android.multiplayer.bluetooth.ServidorBluetoothActivity; import me.chester.minitruco.android.multiplayer.internet.ClienteInternetActivity; +import me.chester.minitruco.android.multiplayer.internet.InternetUtils; import me.chester.minitruco.core.Jogador; import me.chester.minitruco.core.JogadorBot; import me.chester.minitruco.core.Partida; @@ -96,10 +99,7 @@ public void onCreate(Bundle savedInstanceState) { mostraAlertBox(this.getString(R.string.titulo_sobre), stats_versao + this.getString(R.string.texto_sobre)); }); - // TODO ver se tem um modo mais central de garantir este default (e outros) - // (provavelmente quando migrar esse PreferenceManager deprecado - // e começar a centralizar as preferencias nesta view) - selecionaModo(preferences.getString("modo", "P")); + selecionaModo(getLetraDoModo(this)); } /** @@ -150,6 +150,8 @@ private void mostraNotificacaoInicial() { mostraAlertBox(this.getString(R.string.titulo_instrucoes), this.getString(R.string.texto_instrucoes)); } else if (!versaoQueMostrouNovidades.equals(versaoAtual)) { mostraAlertBox("Novidades", this.getString(R.string.novidades)); + } else { + promoveJogoInternet(false); } Editor e = preferences.edit(); @@ -339,13 +341,16 @@ public void modoButtonClickHandler(View view) { .setMessage("Estes modos são jogados com a partida valendo 1 e o truco indo a 3, depois 6, 9 e 12.") .setNegativeButton("Baralho Limpo", (dialog, which) -> { selecionaModo("L"); + promoveJogoInternet(true); }) .setPositiveButton("Manilha Velha", (dialog, which) -> { selecionaModo("V"); + promoveJogoInternet(true); }) .show(); } else { selecionaModo((String) view.getTag()); + promoveJogoInternet(true); } } @@ -354,9 +359,19 @@ private void selecionaModo(String modo) { preferences.edit().putString("modo", modo).apply(); } + private void promoveJogoInternet(boolean repete) { + new Thread(() -> { + if (InternetUtils.isPromoveJogoInternet(this, repete)) { + runOnUiThread(() -> { + Toast.makeText(this, "Jogue pela internet agora! Pessoas estão aguardando por você.", Toast.LENGTH_LONG).show(); + }); + } + }).start(); + } + @Override public Partida criaNovaPartida(JogadorHumano jogadorHumano) { - String modo = preferences.getString("modo", "P"); + String modo = getLetraDoModo(this); boolean humanoDecide = preferences.getBoolean("humanoDecide", true); boolean jogoAutomatico = preferences.getBoolean("jogoAutomatico", false); Partida novaPartida = new PartidaLocal(humanoDecide, jogoAutomatico, modo); diff --git a/app/src/main/java/me/chester/minitruco/android/TrucoActivity.java b/app/src/main/java/me/chester/minitruco/android/TrucoActivity.java index 2485eaef..d001bbc5 100644 --- a/app/src/main/java/me/chester/minitruco/android/TrucoActivity.java +++ b/app/src/main/java/me/chester/minitruco/android/TrucoActivity.java @@ -25,11 +25,13 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; import me.chester.minitruco.R; +import me.chester.minitruco.android.multiplayer.internet.InternetUtils; import me.chester.minitruco.core.Partida; /* SPDX-License-Identifier: BSD-3-Clause */ @@ -367,6 +369,9 @@ public void jogoFechado(int numEquipeVencedora) { // servidor bluetooth (em ambos os casos, estará na posição 1). if (jogadorHumano.getPosicao() == 1) { btnNovaPartida.setVisibility(View.VISIBLE); + if (partida.semJogadoresRemotos()) { + promoveJogoInternet(); + } if (partida.isJogoAutomatico()) { btnNovaPartida.performClick(); } @@ -374,6 +379,16 @@ public void jogoFechado(int numEquipeVencedora) { }); } + private void promoveJogoInternet() { + new Thread(() -> { + if (InternetUtils.isPromoveJogoInternet(this, false)) { + runOnUiThread(() -> { + Toast.makeText(this, "Tem pessoas de verdade aguardando para jogar! Use o botão Internet na tela inicial.", Toast.LENGTH_LONG).show(); + }); + } + }).start(); + } + /** * Determina se o placar de partidas deve ser salvo/recuperado * diff --git a/app/src/main/java/me/chester/minitruco/android/multiplayer/bluetooth/ServidorBluetoothActivity.java b/app/src/main/java/me/chester/minitruco/android/multiplayer/bluetooth/ServidorBluetoothActivity.java index d476624a..cde94293 100644 --- a/app/src/main/java/me/chester/minitruco/android/multiplayer/bluetooth/ServidorBluetoothActivity.java +++ b/app/src/main/java/me/chester/minitruco/android/multiplayer/bluetooth/ServidorBluetoothActivity.java @@ -1,5 +1,6 @@ package me.chester.minitruco.android.multiplayer.bluetooth; +import static me.chester.minitruco.android.PreferenceUtils.getLetraDoModo; import static me.chester.minitruco.core.JogadorBot.APELIDO_BOT; import static me.chester.minitruco.core.TrucoUtils.POSICAO_PLACEHOLDER; import static me.chester.minitruco.core.TrucoUtils.montaNotificacaoI; @@ -61,10 +62,7 @@ public void onReceive(Context context, Intent intent) { @Override void iniciaAtividadeBluetooth() { - SharedPreferences preferences = PreferenceManager - .getDefaultSharedPreferences(this); - // TODO titulo poderia passar como extra do intent - modo = preferences.getString("modo", "P"); + modo = getLetraDoModo(this); layoutBotoesGerente.setVisibility(View.VISIBLE); btnIniciar.setOnClickListener(v -> { status = STATUS_EM_JOGO; diff --git a/app/src/main/java/me/chester/minitruco/android/multiplayer/internet/ClienteInternetActivity.java b/app/src/main/java/me/chester/minitruco/android/multiplayer/internet/ClienteInternetActivity.java index d21c8ed4..02773ac2 100644 --- a/app/src/main/java/me/chester/minitruco/android/multiplayer/internet/ClienteInternetActivity.java +++ b/app/src/main/java/me/chester/minitruco/android/multiplayer/internet/ClienteInternetActivity.java @@ -2,6 +2,9 @@ import static android.text.InputType.TYPE_CLASS_NUMBER; +import static me.chester.minitruco.android.PreferenceUtils.getLetraDoModo; +import static me.chester.minitruco.android.PreferenceUtils.getServidor; + import android.app.AlertDialog; import android.content.SharedPreferences; import android.os.Bundle; @@ -12,7 +15,6 @@ import android.widget.EditText; import android.widget.Toast; -import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; import java.io.BufferedReader; @@ -27,7 +29,6 @@ import java.util.logging.Logger; import me.chester.minitruco.BuildConfig; -import me.chester.minitruco.R; import me.chester.minitruco.android.CriadorDePartida; import me.chester.minitruco.android.JogadorHumano; import me.chester.minitruco.android.SalaActivity; @@ -187,9 +188,7 @@ public void onBackPressed() { } private boolean conecta() { - String servidor = preferences.getBoolean("servidorLocal", false) ? - this.getString(R.string.opcoes_default_servidor_local) : - this.getString(R.string.opcoes_default_servidor); + String servidor = getServidor(this); try { socket = new Socket(); socket.connect(new InetSocketAddress(servidor, 6912), 10_000); @@ -233,7 +232,7 @@ private void processaNotificacoes() { case 'N': // Nome foi aceito // Já vamos entrar de cara numa sala pública (se a pessoa quiser // fazer outra coisa, ela usa o botão apropriado) - enviaLinha("E PUB " + getModoDasPreferencias()); + enviaLinha("E PUB " + getLetraDoModo(this)); break; case 'I': // Entrou/voltou para uma sala (ou ela foi atualizada) exibeMesaForaDoJogo(line); @@ -287,11 +286,6 @@ private void erroFatalSalaInvalida() { "te convidou e tente novamente."); } - @NonNull - private String getModoDasPreferencias() { - return preferences.getString("modo", "P"); - } - /** * Se estivermos numa sala pública e a mesa estiver cheia, inicia contagem * regressiva para auto-início do jogo. diff --git a/app/src/main/java/me/chester/minitruco/android/multiplayer/internet/InternetUtils.java b/app/src/main/java/me/chester/minitruco/android/multiplayer/internet/InternetUtils.java new file mode 100644 index 00000000..5ceba68d --- /dev/null +++ b/app/src/main/java/me/chester/minitruco/android/multiplayer/internet/InternetUtils.java @@ -0,0 +1,68 @@ +package me.chester.minitruco.android.multiplayer.internet; + +/* SPDX-License-Identifier: BSD-3-Clause */ +/* Copyright © 2005-2023 Carlos Duarte do Nascimento "Chester" */ + +import static me.chester.minitruco.android.PreferenceUtils.getLetraDoModo; +import static me.chester.minitruco.android.PreferenceUtils.getServidor; + +import android.content.Context; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.List; + +/** + * Métodos utilitários relacionados ao jogo online. + */ +public class InternetUtils { + + static boolean mostrouConvite = false; + + /** + * Diz se devemos mostrar um toast convidando a pessoa para jogar na + * internet, verificando se existe sala no servidor no modo de jogo + * selecionado que esteja esperando pessoas. + *

+ * O objetivo é reduzir a espera no servidor, mas sem irritar quem + * não quer jogar online, então a resposta positiva só é retornada + * uma vez por execução do aplicativo, a não ser que repete seja true. + * + * + * @param context Context usado para ler preferências e mostrar toast + * @param repete Se true, esquece toasts anteriores (útil para quando + * a pessoa seleciona um modo de jogo diferente) + */ + public static boolean isPromoveJogoInternet(Context context, boolean repete) { + if (repete) { + mostrouConvite = false; + } + if (mostrouConvite) { + return false; + } + + HttpURLConnection urlConnection = null; + try { + URL url = new URL("http://" + getServidor(context) + ":6912/status"); + urlConnection = (HttpURLConnection) url.openConnection(); + BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith("AGUARDANDO ") && line.substring(11).contains(getLetraDoModo(context))) { + mostrouConvite = true; + return true; + } + } + } catch (IOException e) { + return false; + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + } + return false; + } +} diff --git a/app/src/main/res/values/opcoes.xml b/app/src/main/res/values/opcoes.xml index b5b6e9c3..1900a15d 100644 --- a/app/src/main/res/values/opcoes.xml +++ b/app/src/main/res/values/opcoes.xml @@ -1,7 +1,7 @@ minitruco.chester.me - 192.168.2.10 + 192.168.2.100 Normal diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 00000000..5fbea713 --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,7 @@ + + + + minitruco.chester.me + 192.168.2.100 + + diff --git a/app/src/test/java/me/chester/minitruco/android/multiplayer/internet/InternetUtilsTest.java b/app/src/test/java/me/chester/minitruco/android/multiplayer/internet/InternetUtilsTest.java new file mode 100644 index 00000000..94c12482 --- /dev/null +++ b/app/src/test/java/me/chester/minitruco/android/multiplayer/internet/InternetUtilsTest.java @@ -0,0 +1,104 @@ +package me.chester.minitruco.android.multiplayer.internet; + +/* SPDX-License-Identifier: BSD-3-Clause */ +/* Copyright © 2005-2023 Carlos Duarte do Nascimento "Chester" */ + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; + +import java.io.ByteArrayInputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; + +import me.chester.minitruco.android.PreferenceUtils; + +class InternetUtilsTest { + + private MockedStatic mockPreferenceUtils; + private MockedConstruction urlMockedConstruction; + + @AfterEach + public void afterEach() { + if (mockPreferenceUtils != null) { + mockPreferenceUtils.close(); + } + if (urlMockedConstruction != null) { + urlMockedConstruction.close(); + } + } + + void mockModo(String value, String... values) { + if (mockPreferenceUtils != null) { + mockPreferenceUtils.close(); + } + mockPreferenceUtils = mockStatic(PreferenceUtils.class); + mockPreferenceUtils.when(() -> PreferenceUtils.getLetraDoModo(any())).thenReturn(value, values); + } + + void mockAguardando(String modo) { + if (urlMockedConstruction != null) { + urlMockedConstruction.close(); + } + urlMockedConstruction = mockConstruction(URL.class, (theMock, context) -> { + HttpURLConnection mockHttpURLConnection = mock(HttpURLConnection.class); + when(theMock.openConnection()).thenReturn((URLConnection) mockHttpURLConnection); + when(mockHttpURLConnection.getInputStream()).thenReturn(new ByteArrayInputStream(( + "OK\n" + + "ONLINE 123\n" + + (modo != null ? "AGUARDANDO " + modo + "\n" : "") + + "K 12342342342\n" + ).getBytes())); + }); + } + + @Test + void testPromoveJogoInternetSoETrueSeTemSalaAguardandoNoModoAtual() { + mockAguardando("M"); + mockModo("M", "P", "L"); + assertTrue(InternetUtils.isPromoveJogoInternet(null, true)); + assertFalse(InternetUtils.isPromoveJogoInternet(null, true)); + assertFalse(InternetUtils.isPromoveJogoInternet(null, true)); + + mockAguardando("ML"); + mockModo("M", "P", "L"); + assertTrue(InternetUtils.isPromoveJogoInternet(null, true)); + assertFalse(InternetUtils.isPromoveJogoInternet(null, true)); + assertTrue(InternetUtils.isPromoveJogoInternet(null, true)); + + mockAguardando(""); + mockModo("M", "P", "L"); + assertFalse(InternetUtils.isPromoveJogoInternet(null, true)); + assertFalse(InternetUtils.isPromoveJogoInternet(null, true)); + assertFalse(InternetUtils.isPromoveJogoInternet(null, true)); + } + + @Test + void testPromoveJogoInternetIgnoraSeNaoTiverInfoDeAguardandoNoStatus() { + mockAguardando(null); + mockModo("M", "P", "L"); + assertFalse(InternetUtils.isPromoveJogoInternet(null, true)); + assertFalse(InternetUtils.isPromoveJogoInternet(null, true)); + assertFalse(InternetUtils.isPromoveJogoInternet(null, true)); + } + + @Test + void testPromoveJogoInternetNaoPromoveDuasVezesSeNaoPedirRepeticao() { + mockAguardando("M"); + mockModo("M", "M", "M"); + assertTrue(InternetUtils.isPromoveJogoInternet(null, false)); + assertFalse(InternetUtils.isPromoveJogoInternet(null, false)); + assertFalse(InternetUtils.isPromoveJogoInternet(null, false)); + } +} diff --git a/build.gradle b/build.gradle index 44a9d956..7ba34f8f 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.1.2' + classpath 'com.android.tools.build:gradle:8.2.0' } } diff --git a/core/src/main/java/me/chester/minitruco/core/Modo.java b/core/src/main/java/me/chester/minitruco/core/Modo.java index f8ba2249..e0cfbfaf 100644 --- a/core/src/main/java/me/chester/minitruco/core/Modo.java +++ b/core/src/main/java/me/chester/minitruco/core/Modo.java @@ -40,6 +40,10 @@ static boolean isModoValido(String modoStr) { } } + static String[] getModosValidos() { + return new String[] { "M", "P", "V", "L" }; + } + int pontuacaoParaMaoDeX(); int valorInicialDaMao(); diff --git a/gradle.properties b/gradle.properties index c66f67cf..9686bf37 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,12 +11,7 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true #Sat Jun 17 18:53:44 EDT 2023 -android.defaults.buildfeatures.buildconfig=true android.nonFinalResIds=false android.nonTransitiveRClass=false android.useAndroidX=true -org.gradle.unsafe.configuration-cache=true -# server uses Virtual Threads (JEP 436); we need to enable on the compiler... -android.experimental.enableJavaPreview=true -# ...and on the VM -org.gradle.jvmargs=-Xmx2048M --enable-preview +org.gradle.unsafe.configuration-cache=true \ No newline at end of file diff --git a/server/build.gradle b/server/build.gradle index 192c4b38..e15523a8 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -23,7 +23,6 @@ test { testLogging { events "passed", "skipped", "failed" } - jvmArgs += '--enable-preview' } tasks.jar { diff --git a/server/src/main/java/me/chester/minitruco/server/JogadorConectado.java b/server/src/main/java/me/chester/minitruco/server/JogadorConectado.java index e9661565..ad280306 100644 --- a/server/src/main/java/me/chester/minitruco/server/JogadorConectado.java +++ b/server/src/main/java/me/chester/minitruco/server/JogadorConectado.java @@ -108,6 +108,21 @@ public void run() { // Como o SO_TIMEOUT está em zero, podemos assumir desconexão break; } + if (linha != null && linha.startsWith("GET /status HTTP/")) { + // Essa parte (que envia o OK) tem que rodar em menos de 1s, + // para evitar que o keepalive seja enviado antes do header + // HTTP (vide iniciaThreadAuxiliar()) + LOGGER.info("Status do servidor solicitado (HTTP); enviando e desconectando"); + out.println("HTTP/1.1 200 OK"); + out.println("Content-Type: text/plain"); + out.println(); + out.println("OK"); + out.flush(); + + out.println(MiniTrucoServer.status()); + out.flush(); + return; + } if (("K " + keepAlive).equals(linha)) { keepAlive = 0; continue; diff --git a/server/src/main/java/me/chester/minitruco/server/MiniTrucoServer.java b/server/src/main/java/me/chester/minitruco/server/MiniTrucoServer.java index 4d556a8f..426a3fa3 100644 --- a/server/src/main/java/me/chester/minitruco/server/MiniTrucoServer.java +++ b/server/src/main/java/me/chester/minitruco/server/MiniTrucoServer.java @@ -168,4 +168,17 @@ private static void aguardaThreadsJogadoresFinalizarem() { } LOGGER.info("Todos os jogadores finalizaram."); } + + /** + * Gera o status do servidor (a ser retornado logo após o OK quando + * for solicitada via HTTP a URL /status). + *

+ * Quem processa a URL (e usa este método) é o JogadorConectado + * + * @return linhas no formato "CHAVE valor", separadas por \n + */ + public static String status() { + return "ONLINE " + (threadsJogadores.size() - 1) + "\n" + + "AGUARDANDO " + Sala.modosAguardandoJogadores(); + } } diff --git a/server/src/main/java/me/chester/minitruco/server/Sala.java b/server/src/main/java/me/chester/minitruco/server/Sala.java index 2a8e93c5..8f06a698 100644 --- a/server/src/main/java/me/chester/minitruco/server/Sala.java +++ b/server/src/main/java/me/chester/minitruco/server/Sala.java @@ -14,6 +14,7 @@ import me.chester.minitruco.core.Jogador; import me.chester.minitruco.core.JogadorBot; +import me.chester.minitruco.core.Modo; import me.chester.minitruco.core.Partida; import me.chester.minitruco.core.PartidaLocal; @@ -138,6 +139,24 @@ public static synchronized Sala colocaEmNovaSala(JogadorConectado j, boolean pub return sala; } + /** + * Retorna os modos que estão precisando de jogadores em salas públicas, + * para que o cliente possa convidar esses jogadores quando eles abrem + * o app ou selecionam um novo modo. + * + * @return String com as letras dos modos. Ex.: "MP" significa que há + * salas aguardando jogadores para os modos mineiro e paulista. + */ + public static synchronized String modosAguardandoJogadores() { + StringBuilder sb = new StringBuilder(); + for (String modo : Modo.getModosValidos()) { + if (salasPublicasDisponiveis.stream().anyMatch(s -> s.modo.equals(modo))) { + sb.append(modo); + } + } + return sb.toString(); + } + /** * Adiciona um jogador na primeira posição disponível da sala, * garantindo os links bidirecionais e, se necessário, diff --git a/server/src/test/java/me/chester/minitruco/server/SalaTest.java b/server/src/test/java/me/chester/minitruco/server/SalaTest.java index f5f3d7b4..30d82fb7 100644 --- a/server/src/test/java/me/chester/minitruco/server/SalaTest.java +++ b/server/src/test/java/me/chester/minitruco/server/SalaTest.java @@ -19,6 +19,8 @@ import org.junit.jupiter.api.Test; import java.net.Socket; +import java.util.Arrays; +import java.util.stream.Collectors; import me.chester.minitruco.core.Jogador; import me.chester.minitruco.core.Partida; @@ -398,4 +400,31 @@ void testSalaPrivadaGeraCodigoNumericoDeCincoDigitos() { Sala s = new Sala(false, "P"); assertThat(s.codigo, matchesPattern("[0-9]{5}")); } + + String sorted(String s) { + return Arrays.stream(s.split("")).sorted().collect(Collectors.joining()); + } + + @Test + void testModosAguardandoJogadores() { + Sala.colocaEmSalaPublica(j1, "P"); + Sala.colocaEmSalaPublica(j2, "P"); + Sala.colocaEmSalaPublica(j3, "M"); + Sala.colocaEmSalaPublica(j4, "L"); + Sala.colocaEmSalaPublica(j5, "P"); + Sala.colocaEmSalaPublica(j6, "P"); + Sala.colocaEmSalaPublica(j7, "M"); + Sala.colocaEmSalaPublica(j8, "L"); + assertEquals("LM", sorted(Sala.modosAguardandoJogadores())); // P está cheia; V está vazia + + Sala.colocaEmSalaPublica(j9, "P"); + assertEquals("LMP", sorted(Sala.modosAguardandoJogadores())); // Abriu uma nova sala P para o j9 + + j9.getSala().remove(j9); + assertEquals("LM", sorted(Sala.modosAguardandoJogadores())); // A sala fechou automaticamente com 1 jogador + + Sala.colocaEmSalaPublica(j10, "L"); + Sala.colocaEmSalaPublica(jj1, "L"); + assertEquals("M", sorted(Sala.modosAguardandoJogadores())); // Completou a sala L + } }