How to optimize the naval battle game in JAVA?

Question:

I don't know if this is allowed here on the site, so if I'm breaking any rules let me know!

I recently started to learn the JAVA language. I made a naval battle game (human vs computer) using everything I learned so far and I would like to hear opinions from more experienced programmers about the game in order to improve my knowledge.

Some points like what would you have done differently? How would you optimize existing code? Did I commit any redundancy or write more than necessary?

How did I think the game's programming?

I created two 5×5 arrays . One stores the game board. It starts with the default value of 0 for all elements. With a method, I draw 3 of these elements, which will represent the position of the boats to be sunk. To them I assign the value 1. The other array stores the positions where the player shot, 0 symbolizes an area where it has not been attacked yet and 1 an area where it has been attacked.

To display the board, I cross the two arrays in one check. Where it has not yet been attacked, it is written as "~", where it has been attacked and there is no ship as "*" and where it has been attacked and a ship has sunk as "X". I created a method so that every turn a tip is displayed informing the number of existing ships in the row and column of the last shot performed.

Code :

Battleship.java

package batalhanaval;
import java.util.Scanner;

public class BatalhaNaval
{
    public static void main(String[] args)
    {
        Scanner entrada = new Scanner(System.in);
        Tabuleiro tab = new Tabuleiro();
        char option; //Opção do menu principal

        do
        {
            showMenu();
            option = entrada.next().charAt(0);
            System.out.println("\n\n");
            switch(option)
            {
                case '1': //Novo jogo
                {
                    initGame(entrada, tab);
                    break;
                }
                case '2':
                {
                    System.out.println("O seu objetivo no jogo é afundar os 3 navios inimigos que encontram-se no seu litoral.\n"
                            + "Para fazer um disparo, informe a posição(linha e coluna) do seu mapa na qual deseja lançar o míssel.\n"
                            + "O símbolo ~ indica uma área ainda não conhecida.\n"
                            + "O símbolo * indica uma área na qual você disparou e não havia nenhum navio.\n"
                            + "O símbolo X indica uma área onde você afundou um navio inimigo.\n"
                            + "A cada disparo realizado você receberá uma dica informando se há navios na linha e na coluna nas quais você realizou o disparo.");
                    break;
                }
                case '3': //Recorde
                {
                    if(tab.recorde == 1000)
                    {
                       System.out.println("<Recorde atual: nenhum>");
                    }
                    else
                    {
                       System.out.println("<Recorde atual: " + tab.recorde + " turnos>");
                    }
                    break;
                }
                case '4':
                {
                    System.out.println("Finalizando...");
                    break;
                }
                default:
                {
                    System.out.println("Opção inválida!");
                }
            }
            System.out.println("\n");         
        }while(option != '4');

    }
    //Método que retorna true caso a String seja um número inteiro
    public static boolean isInt(String str)
    {
        try 
        {  
            Integer.parseInt(str.trim());
            return true;     
        } 
        catch (NumberFormatException ex) 
        {  
            return false;         
        }  
    }
    //Main menu
    public static void showMenu()
    {
        System.out.println("Bem vindo ao jogo <Batalha Naval>!!! (Criado por Talendar)\nEscolha uma opção:");
        System.out.println("1 - Novo jogo");
        System.out.println("2 - Tutorial");
        System.out.println("3 - Recorde");
        System.out.println("4 - Sair");
    }
    //Jogo
    public static void initGame(Scanner entrada, Tabuleiro tab)
    {
        String linha = ""; //As variáveis linha e coluna foram criadas como String para poderem ser submetidas a checagem do método isInt(str) localizado abaixo.
        String coluna = "";

        tab.initTab();
        tab.initShips();
        do
        {
            System.out.println("\n");
            //Exibe a dica a partir do segundo turno
            if(tab.turno > 1)
            {
                tab.dica(Integer.parseInt(linha), Integer.parseInt(coluna));
            }
            System.out.println();

            //Exibe o tabuleiro em seu estado atual
            tab.printTab();

            //Pede a linha
            System.out.print("Linha:");
            linha = entrada.next();
            while(!isInt(linha))
            {
                System.out.println("Apenas números inteiros de 1 a 5!\nLinha:");
                linha = entrada.next();
            }
            while(Integer.parseInt(linha) < 1 || Integer.parseInt(linha) > 5)
            {
                System.out.println("Apenas números inteiros de 1 a 5!\nLinha:");
                linha = entrada.next();
            }

            //Pede a coluna
            System.out.print("Coluna:");
            coluna = entrada.next();
            while(!isInt(coluna))
            {
                System.out.println("Apenas números inteiros de 1 a 5!\nColuna:");
                coluna = entrada.next();
            }
            while(Integer.parseInt(coluna) < 1 || Integer.parseInt(coluna) > 5)
            {
                System.out.println("Apenas números inteiros de 1 a 5!\nColuna:");
                coluna = entrada.next();
            }
            System.out.println("\n\n");

            //Tiro
            tab.tiro(Integer.parseInt(linha), Integer.parseInt(coluna));
            System.out.println();

        }while(tab.acertos != 3);
        System.out.println("\nVOCÊ DERROTOU O INIMIGO!!!! Turnos: " + tab.turno);
        if(tab.turno < tab.recorde)
        {
            tab.recorde = tab.turno;
            System.out.println("\nNOVO RECORDE (" + tab.recorde + ")!!!!");
        }
    }
}

Tabuleiro.java

/* Água não descoberta(~)
   Água vazia(*): 0
   Navio(X): 1
*/
package batalhanaval;
import java.util.Random;

public class Tabuleiro
{
    int turno = 1; //Turno em questão
    int acertos = 0; //Armazena o número de acertos
    int recorde = 1000; //Armazena o recorde
    int[][] tab = new int[5][5]; //Tabuleiro
    int[][] tiros = new int[5][5]; //Armazeana as posições dos tiros dados: 0 para área desconhecida e 1 para área onde se atirou.

    // Inicia o tabuleiro com o valor padrão 0
    public void initTab()
    {
        acertos = 0;
        turno = 1;
        for(int[] i: tab)
        {
            for(int j = 0; j < i.length; j++)
            {
                i[j] = 0;
            }
        }
    }
    //Sorteia os navios
    public void initShips()
    {
        Random rand = new Random();
        int i; //Linha
        int j; //Coluna

        for(int n = 0; n < 3; n++)
        {
            do
            {
                i = rand.nextInt(5);
                j = rand.nextInt(5);  
            }while(tab[i][j] != 0);
            tab[i][j] = 1;
        }
    }
    //Mostra o tabuleiro de inteiros
    public void printTabInt()
    {
        System.out.print("     (1)  (2)  (3)  (4)  (5)\n\n");
        for(int i = 0; i < tab.length; i++)
        {
            System.out.print("("+ (i+1) + ")  ");
            for(int j = 0; j < tab[i].length; j++)
            {
                System.out.print(" "+tab[i][j]+"   ");
            }
            System.out.println("\n");
        }
    }
    //Mostra o tabuleiro de jogo
    public void printTab()
    {
        System.out.print("     (1)  (2)  (3)  (4)  (5)\n\n");
        for(int i = 0; i < tab.length; i++)
        {
            System.out.print("("+ (i+1) + ")  ");
            for(int j = 0; j < tab[i].length; j++)
            {
                if(tiros[i][j] == 1)
                {
                    if(tab[i][j] == 1)
                    {
                        System.out.print(" X   ");
                    }
                    else
                    {
                        System.out.print(" *   ");
                    }
                }
                else
                {
                    System.out.print(" ~   ");
                }
            }
            System.out.println("\n");
        }
    }
    //Tiro
    public void tiro(int linha, int coluna)
    {
        if(tiros[linha-1][coluna-1] == 0) //Checa se já foi dado tiro na posição fornecida
        {
            tiros[linha-1][coluna-1] = 1; //Muda o valor da posição fornecida para que conste como uma área atirada
            if(tab[linha-1][coluna-1] == 1) //Checa se há um navio na posição fornecida
            {
                System.out.println("Você afundou um navio inimigo!!!");
                acertos++; //Aumenta +1 em acertos
            }
            else
            {
                System.out.println("Você atingiu a água...");
            }
            turno++; //Avança o turno
        }
        else
        {
            System.out.println("Você já atirou nessa posição!");
        }
    }
    //Dica: informa o turno e se há alguma navio na linha e na coluna do último tiro disparado
    public void dica(int linha, int coluna)
    {
        System.out.println("Turno: " + turno);

        int countL = 0;
        for(int i: tab[linha-1])
        {
            if(i == 1)
            {
                countL++;
            }
        }
        System.out.println("Dica: há " + countL + " navio(s) na linha " + linha);

        int countC = 0;
        for(int[] i: tab)
        {
            if(i[coluna-1] == 1)
            {
                countC++;
            }
        }
        System.out.println("      há " + countC + " navio(s) na coluna " + coluna);
    }
}

Answer:

I believe you are searching for more knowledge about good code practices, object orientation, automated testing, and so on.

I suggest that you yourself have the critical sense to self-evaluate your code in order to find faults. Good programmers learn from their mistakes.

To try to increase your knowledge, I will suggest some topics with links for you to delve deeper into the subject. Here are some:

TDD (Test Driven Design):

Test-based software development. You evolve your code just for the test to pass. This makes it easier for you to build a better design of your architecture and objects, ensuring simplicity, loose coupling and automatic coverage of your tests.

Object Orientation

It ensures that your system has classes with a single purpose and that they communicate with each other, guaranteeing their maintainability, usability and evolution of the system. In case you're interested, I've created 10 rules to ensure your code follows object-oriented best practices based on the book "The ThoughtWorks Anthology: Essays on Software".

https://github.com/matheusmessora/OOP-MANIFESTO


I'll cite some examples of your code that breaks some rules:

Rule 4 : Only 1 point per line Improves code readability.

The code snippet below violates this rule, making it difficult to read.

tab.dica(Integer.parseInt(linha), Integer.parseInt(coluna));

A best practice would be Integer line = Integer.parseInt(line); Integer column = Integer.parseInt(column); tab.tip(row, column);

Rule 9 : Method must have only 7 lines of code. This ensures that your methods are highly cohesive, meaning they only do what they're supposed to do, and nothing else. Your printTab method for example breaks the rule.


Finally, I should add that every method must be in the infinitive. This is a good practice adopted in many object-oriented languages. In this case, the method tab.tip should be changed to tab.exibirDica(…)

Scroll to Top