Em vez de colocar um while
dentro de outro, com várias variáveis para controlá-los, acho que fica mais simples se você quebrar cada parte em métodos, assim cada loop fica isolado e mais fácil de gerenciar.
Outro ponto é que se você mistura nextInt
com next
e podem ocorrer alguns problemas (leia aqui para entender melhor). O melhor, quando a entrada for do teclado, é ler a linha inteira sempre com nextLine
e tratar o dado de acordo (por exemplo, se for um número, convertê-lo com Integer.parseInt
, etc).
E no último loop eu usaria o que eu já sugeri acima (nada de criar essa variável pergunta
com uma string, sendo que você pode usar o valor numérico diretamente). E eu também disse que não precisa fechar o System.in
.
E procure seguir as convenções de nomenclatura da linguagem: classes devem ter NomesDesseJeito
em vez de nomes_desse_jeito
.
No while (!enforcou || acertou == false)
, por que você usou o operador !
em um e == false
em outro? Use !
em ambos, ué - ou seja: while (!enforcou || !acertou)
. Se bem que no código que eu sugeri abaixo acabei nem fazendo desta forma.
Se uma variável só é usada em algum loop interno, não precisa declará-la logo no início do programa. Prefira declarar variáveis mais próximo de onde são usadas, pois isso também ajuda a delimitar seu escopo.
Na classe que desenha a forca, em vez de ter os métodos primeiroErro
, segundoErro
, etc, poderia ter um único método que desenha, recebendo a quantidade de erros como parâmetro. Aí lá dentro você decide como isso deve ser desenhado.
Enfim, uma sugestão seria:
// em vez de métodos primeiroErro, segundoErro, etc, faça um genérico que recebe a quantidade de erros
public class DesenhoForca { // mudei o nome para começar com maiúscula
public void desenha(int erros) {
// aqui você decide como desenhar, de acordo com a quantidade de erros
// pode até ser o switch, que chama os métodos primeiroErro, segundoErro, etc
// enfim, se tem uma classe que sabe desenhar a forca, eu prefiro deixar toda essa lógica dentro dela
}
}
public class AdvForca { // mudei o nome para começar com maiúscula
// ler um número, verificar se está entre os valores válidos
private static int lerNumero(String prompt, Scanner scanner, int valorMinimo, int valorMaximo) {
while (true) {
try {
System.out.println(prompt);
int valor = Integer.parseInt(scanner.nextLine());
if (valor < valorMinimo || valor > valorMaximo) {
System.out.printf("Valor inválido, deve estar entre %d e %d\n", valorMinimo, valorMaximo);
} else {
return valor; // retorna o valor lido
}
} catch (NumberFormatException e) {
System.out.println("Você não digitou um número");
}
}
}
private static int lerDificuldade(Scanner scanner) {
String prompt = "A dificuldade definirá a complexidade da palavra misteriosa\n"
+ "1- fácil \n"
+ "2- médio \n"
+ "3- difícil \n"
+ "Informe o número da dificuldade escolhida:";
return lerNumero(prompt, scanner, 1, 3);
}
private static char lerChute(Scanner sc, Set<String> chutesDados) {
while (true) {
System.out.println("Vc já chutou as letras:" + String.join(" ", chutesDados));
System.out.println("\nDigite a letra que deseja chutar:");
// lê o chute e converte para minúscula
String chute = sc.nextLine().substring(0, 1).toLowerCase();
if (chutesDados.contains(chute)) {
System.out.println("Você já chutou esta letra antes, escolha outra\n");
} else {
chutesDados.add(chute);
return chute.charAt(0);
}
}
}
// loop do jogo propriamente dito (onde o usuário vai tentar adivinhar a palavra)
private static void gameLoop(Scanner sc, String palavraJogo, DesenhoForca desenhaForca) {
int erros = 0;
char acertos[] = new char[palavraJogo.length()];
for (int i = 0; i < palavraJogo.length(); i++) {
acertos[i] = '_'; // o próprio array de acertos possuirá os caracteres que foram adivinhados
}
// guarde os chutes em um java.util.Set, que não permite duplicados e é rápido para verificar se um elemento já existe
Set<String> chutesDados = new TreeSet<>(); // java.util.TreeSet guarda os valores em ordem alfabética, ótimo para mostrar na tela
while (true) { // jogo
System.out.print("A palavra misteriosa é: ");
for (char c : acertos) {
System.out.printf(" %c", c);
}
System.out.println();
char chute = lerChute(sc, chutesDados);
// verifica se o chute está certo
boolean acertou = false;
for (int i = 0; i < palavraJogo.length(); i++) {
if (chute == palavraJogo.charAt(i)) {
acertos[i] = chute; // atualiza o array de acertos
acertou = true;
}
}
// limpaTela era chamado no if e no else, então ele sempre é chamado
desenhaForca.limpaTela();
if (acertou) {
// verifica se venceu
String palavraAcertos = new String(acertos);
if (palavraJogo.equals(palavraAcertos)) {
desenhaForca.venceu();
return; // se venceu, sai do jogo
}
} else {
erros++;
desenhaForca.desenha(erros);
if (erros == 6) { // se já errou bastante, acabou o jogo
desenhaForca.endGame();
System.out.printf("\nA palavra era:%s\n", palavraJogo);
return; // sai do jogo
}
}
}
}
private static int lerOpcaoSair(Scanner scanner) {
return lerNumero("Deseja continuar (1-sim/2-nao)?\n\nDigite o numero correspondente:", scanner, 1, 2);
}
public static void main(String[] args) {
DesenhoForca desenhaForca = new DesenhoForca();
Scanner sc = new Scanner(System.in);
String palavraMisteriosa[] = {"banana", "melancia", "granadilha"};
while (true) {
int dificuldade = lerDificuldade(sc);
String palavraJogo = palavraMisteriosa[dificuldade - 1];
gameLoop(sc, palavraJogo, desenhaForca);
System.out.println("Fim de jogo\n");
// se escolheu a opção de sair, então sai do while
if (lerOpcaoSair(sc) == 2) {
break;
}
}
}
}
Criei um método genérico para ler um número inteiro, e reaproveitei ele para ler a dificuldade e a opção de sair. Para ler os chutes eu usei um java.util.Set
que guarda as letras já chutadas (melhor que ficar concatenando strings, pois em um Set
você pode verificar facilmente as letras que já foram tentadas antes). E usando um TreeSet
eu garanto que elas estarão em ordem alfabética, o que acho que fica mais organizado na hora de imprimir. Tem mais comentários no próprio código, explicando os detalhes que eu mudei.
Veja como o main
fica mais simples e organizado, usando os métodos criados. Dentro de cada método você trata a complexidade particular de cada um.
Assim fica melhor do que ter vários loops, um dentro de outro, e muitas variáveis para controlar cada um. Tendo métodos separados, cada um deles individualmente fica mais simples, e a integração entre eles também fica mais fácil de fazer e entender.
Se quiser, dá pra deixar o main
um pouco mais enxuto, eliminando algumas variáveis:
public static void main(String[] args) {
DesenhoForca desenhaForca = new DesenhoForca();
Scanner sc = new Scanner(System.in);
String palavraMisteriosa[] = {"banana", "melancia", "granadilha"};
while (true) {
gameLoop(sc, palavraMisteriosa[lerDificuldade(sc) - 1], desenhaForca);
System.out.println("Fim de jogo\n");
// se escolheu a opção de sair, então sai do while
if (lerOpcaoSair(sc) == 2) {
break;
}
}
}
Mas aí você tem que avaliar se isso prejudica a legibilidade (nesse caso eu diria que nem tanto). Código menor não é necessariamente melhor. Mas criar variáveis desnecessárias também não é uma boa. Encontrar o ponto de equilíbrio nem sempre é fácil e costuma ser algo bem opinativo.