Sander Sander - 4 months ago 15
Java Question

Java Batteship: text is printed twice instead of once in case of a hit

I wrote a piece of code to implement a Battleship game. The method

isHit
is meant to check whether player input leads to hitting a ship or not. It does so by looping through an int[3][2] (
ships
) holding coordinates of the ships and comparing these with the coordinates provided by the user. If it's a hit,
isHit
prints text to let the user know they hit a ship. However, while running the code and hitting a ship, the text is printed to the console twice instead of once. I can't seem to figure out why, so I would be glad if someone could help me out here. Full code is included below:

import java.util.Random;
import java.util.Scanner;

public class BattleShip {

static int[][] board = new int[5][5];
static int[][] ships = new int[3][2];
static int row, column;
static int[] playerAttempt = new int[2];
static int nrOfAttempts = 0, nrOfHits = 0;

public static void welcome() {
//Welcomes the user and introduces rules of the game
System.out.println("WELCOME TO BATTLESHIP!");
System.out.println("In this game, it's your task to localize the three ships that are present in the see.");
System.out.println("Now, the board is filled with ~ signs, representing waves.");
System.out.println("You will be asked to provide coordinates, first a row number and then a column number.");
System.out.println("When you hit a ship at the provided coordinates, an X will appear on the map. If you miss, a 0 will appear.");
System.out.println("You have unlimited attempts to sink the ships, but the less attempts, the better.");
System.out.println("Good luck!\n");
}

public static void createBoard(int[][] board) {
//creates a new playing board of 5x5
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
board[i][j] = 0;
}
}
}

public static void placeShips(int[][] ships) {
//places 3 battleships at random places in the board

Random randomCoordinate = new Random();

//generates random coordinates for the first ship
for (int i = 0; i < ships.length; i++) {
ships[i][0] = randomCoordinate.nextInt(5);
ships[i][1] = randomCoordinate.nextInt(5);

//checks if coordinates of new ship are the same as previous ship
//by using j<i in for-statement, this will only be checked from the second ship onwards
//if coordinates are the same, new random coordinates are generated
for (int j = 0; j < i; j++) {
if (ships[i][0] == (ships[j][0]) && ships[i][1] == (ships[j][1])) {
do {
ships[i][0] = randomCoordinate.nextInt(5);
ships[i][1] = randomCoordinate.nextInt(5);
} while (ships[i][0] == (ships[j][0]) && ships[i][1] == (ships[j][1]));
}
}
}
}

public static void showBoard(int[][] board) {
//displays the board to the user
System.out.println("Playing board:\n");
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
if (board[i][j] == 0) {
System.out.print("~ "); //represents unknown territory
} else if (board[i][j] == 1) {
System.out.print("0 "); //represents a miss
} else {
System.out.print("X "); //represents a hit
}
}
System.out.println("\t"); //prints a return after each row
}
System.out.println(""); //prints a blank line below the board
}

public static void printShips(int[][] ships) {
//shows coordinates of ships for testing purposes
System.out.println("Ships are located at these 3 coordinates:");
for (int i = 0; i < ships.length; i++) {
for (int j = 0; j < ships[i].length; j++) {
System.out.print((ships[i][j]+1)+" ");
}
System.out.println("\t");
}
System.out.println("");
}

public static boolean isWithinBoundaries (int input) {
if (input >= 1 && input <= 5) {
//input is within boundaries of the playing board
return true;
} else {
//input is outside boundaries of the playing board
return false;
}
}

public static int pickRow() {
//method to let the user pick a row

Scanner rowInput = new Scanner(System.in);
String response; //temporary variable to store user input

while (true) { /*loop is infinite as long as input is invalid.
as soon as it's valid, the method exits the loop by
the return statement*/

//checks if user input is an integer by using try-catch statement
System.out.print("Pick a row (1-5): ");
response = rowInput.nextLine();

try {
row = Integer.valueOf(response);
} catch (NumberFormatException e) {
System.out.println("Sorry, invalid input. Please provide a number from 1 to 5.");
continue;
}

if (!isWithinBoundaries(row))
{ //checks if user input is within boundaries of the playing board
System.out.println("That's outside the sea. Please provide a number from 1 to 5.");
} else {
row = row - 1; //adjusts value because Java starts counting at 0, not 1
return row; //value is valid, so return
}
}
}


public static int pickColumn () {
//method to let the user pick a column

Scanner columnInput = new Scanner(System.in);
String response; //temporary variable to store user input

while (true) { /*loop is infinite as long as input is invalid
as soon as it's valid, the method exits the loop by
returning*/

//checks if user input is an integer by using try-catch statement
System.out.print("Pick a column (1-5): ");
response = columnInput.nextLine();

try {
column = Integer.valueOf(response);
} catch (NumberFormatException e) {
System.out.println("Sorry, invalid input. Please provide a number from 1 to 5.");
continue;
}

if (!isWithinBoundaries(column))
{ //checks if user input is within boundaries of the playing board
System.out.println("That's outside the sea. Please provide a number from 1 to 5.");
} else {
column = column - 1; //adjusts value because Java starts counting at 0, not 1
return column; //value is valid, so return
}
}
}

public static void playerAttempt(int[] playerAttempt) {
//method that incorporates player's picks of row and column into an attempt
playerAttempt[0] = pickRow();
playerAttempt[1] = pickColumn();
}

public static boolean isHit(int[][] ships, int[] playerAttempt) {
//checks whether the player attempt hits a ship
for (int i = 0; i < ships.length; i++) {
if (playerAttempt[0] == ships[i][0] && playerAttempt[1] == ships[i][1]) {
System.out.println("That's a hit!");
System.out.println("You sunk the ship located at coordinates "+(playerAttempt[0]+1)+","+(playerAttempt[1]+1)+".\n");
return true;
}
}
return false;
}

public static void adaptBoardAfterAttempt (int[] playerAttempt, int[][] ships, int[][] board) {
//adapts the playing board after a player attempt to indicate a hit (X) or a miss (0)
if (isHit(ships,playerAttempt)) {
board[playerAttempt[0]][playerAttempt[1]]=2;
} else {
board[playerAttempt[0]][playerAttempt[1]]=1;
}
}

public static void main(String[] Args) {
//carries out the main program

welcome();
createBoard(board);
placeShips(ships);
//printShips(ships);

//player has opportunity to make attempts until all 3 ships are hit
do {
showBoard(board);
playerAttempt(playerAttempt);
nrOfAttempts++;

if (isHit(ships, playerAttempt)) {
nrOfHits++;
} else {
System.out.println("You missed! But keep trying...\n");
}

adaptBoardAfterAttempt(playerAttempt,ships,board);

} while (nrOfHits <= 2);

//finishes the game
System.out.println("\nBATTLESHIP IS OVER.\n");
showBoard(board);
System.out.println("");
System.out.println("You needed "+nrOfAttempts+" attempts to sink all ships.");

}
}

Answer

Your code calls the isHit method twice after a hit.

First time at the correct place inside the main method:

First call

After this you call adaptBoardAfterAttempt(playerAttempt, ships, board); inside the main method but this leads to the second call of isHit:

Second call

You may fix the problem by not letting isHit print itself. Then add the print logic inside your main:

if (isHit(ships, playerAttempt)) {
    nrOfHits++
    // Add the print stuff here
    System.out.println("Hit...");
} else { ...

How can you debug such stuff on your own next time? Are you using an IDE like Eclipse? If so, you may add a breakpoint where the prints are made (you do so by double-clicking on the line number of this line).
After that, start the program in the debugging mode, that is the little bug here: Debug-Mode Eclipse

Play the game until the first print, then use Step-Over (F6) to execute the next line of the code and then stop again: Step-Over

Repeat the process until you reach the isHit method again, then you see why it was called again :)

You also can simply click on the isHit method, then Eclipse will highlight all occurrences of this method. Then you will immediately see at which places the problem may occur.