0

So I am making a simple battle ships game (I am fairly new to java...) and I am getting an unexpected result when assigning the locations of 'ships' to a virtual grid of 7x7. I'll start with showing the test code and the output from that code:

        Ship[] ships = new Ship[2];

        for (int i = 0; i < ships.length; i++) {
            ships[i] = new Ship();
            ships[i].setLocationCells(ShipLocations.createLocations());
            System.out.println(Arrays.deepToString(ships[i].getLocationCells()));
        }

        for (int i = 0; i < ships.length; i++) {
            System.out.println(Arrays.deepToString(ships[i].getLocationCells()));
        }
        System.out.println(Arrays.deepToString(ShipLocations.getUsedLocations()));

So I am just initializing an array that holds two Ship objects. I loop through and create those objects. ShipLocations.createCells() returns a 3x2 2D array of random unique locations. Set and get methods are pretty standard. The output of this code is this:

[[1, 3], [1, 4], [1, 5]]
[[4, 5], [5, 5], [6, 5]]
[[4, 5], [5, 5], [6, 5]]
[[4, 5], [5, 5], [6, 5]]
[[1, 3], [1, 4], [1, 5], [4, 5], [5, 5], [6, 5]}

So it appears from this that when it is initially set, the positions are indeed unique but in the second for loop where I go back to check the locations, it appears that both object's location cells have been set to the last values generated from ShipLocations.createLocationCells(). The last print is showing all the locations that have created and stored by ShipLocations (as you can see those values are unaffected and still unique). So what is going on here? Why is the last call to setLocationCells() resetting the member variable for both Ship() instances? I am wondering if there is a reference pointer problem here that I am just missing somehow... Do the ship reference variables point to the same object and that's what is happening? Here is the Ship class if that helps with any clarification questions:

import java.util.Arrays;

public class Ship {
    private int len = 3;
    private int[][] locationCells = new int[3][2];
    private int numOfHits = 0;

    public String check(int[] g) {
        // compare user guess to location cell
        for (int i = 0; i < locationCells.length; i++) {
            //look for hit, if guess is a location...
            if (locationCells[i][0] == g[0] && locationCells[i][1] == g[1]) {
                // increase num of hits
                numOfHits++;
                // shrink locationsCells and remove the hit location
                shrinkLocations(g);
                
                if (locationCells.length == 0) {
                    return "kill";
                }
                else {
                    return "hit";
                }
            }
        }
        
        return "miss";
    }

    public void shrinkLocations(int[] guess) {
        int[][] temp = new int[locationCells.length - 1][2];
        int currentPos = 0;

        for (int i = 0; i < locationCells.length; i++) {
            if (!(locationCells[i][0] == guess[0] && locationCells[i][1] == guess[1])) {
                temp[currentPos] = locationCells[i];
                currentPos++;
            }
        }
        setLocationCells(temp);
    }
    
    public void setLocationCells(int[][] locations) {
        this.locationCells = locations;
    }
    public int[][] getLocationCells() {
        return this.locationCells;
    }
    public void setLen(int len) {
        this.len = len;
    }
    public int getLen() {
        return len;
    }
    public int getNumOfHits() {
        return numOfHits;
    }
    
}

Here is the ShipLocations class (by request):

mport java.util.Random;

public class ShipLocations {
    // array that holds the nine total ship locations
    private static int len = 3;
    private static int[][] usedLocations = new int[9][2];
    private static int[][] shipLocations = new int[len][2];
    
    public static int[][] createLocations(){
        // need to know if you are going to position ship up or down.
        Random randint = new Random();
        // control boolean for do-while loop below
        boolean locationsHaveBeenUsed = false; 
        boolean placementIsValid = false;
        int xLoc;
        int yLoc;
        int direction = randint.nextInt(2);
        // generate locations until the initial location isnt already used
        do {
            // x location starts at 1 and goes to a point where it will still fit on the board
            xLoc = randint.nextInt(8 - len) + 1;
            // y location starts at 1 and goes to a point where it will still fit on the board
            yLoc = randint.nextInt(8 - len) + 1;

            locationsHaveBeenUsed = hasBeenUsed(xLoc, yLoc);
            
            //generate new direction and try again.
            direction = randint.nextInt(2);
            placementIsValid =  validPlacement(xLoc, yLoc, direction);
            // only place if the locations have not been NOT been used
            if (placementIsValid && !locationsHaveBeenUsed) {
                //make a call to place at those locations.
                placeShipLocations(xLoc, yLoc, direction);
            }
        // stop once both the locations haven't been used and the placement is not valid    
        } while (locationsHaveBeenUsed || !placementIsValid);
        
        // current shipLocations array has been altered, return.
        return shipLocations;

    }

    public static boolean hasBeenUsed(int xLoc, int yLoc) {
        
        for (int[] loc : usedLocations) {
            // if the randomly generated location has already been used, generate again.
            if (loc[0] == xLoc && loc[1] == yLoc) {
                return true;
            }
        }
        // if not found in usedLocaations return false
        return false;
    }

    public static void placeInNonEmpty(int xLoc, int yLoc) {
        // add the location to used locations in the slot that is first available (non-double zeros)
        for (int j = 0; j < usedLocations.length; j++) {
            if (usedLocations[j][0] == 0 && usedLocations[j][1] == 0) {
                usedLocations[j][0] = xLoc;
                usedLocations[j][1] = yLoc;
                break; 
            }
        }
    }

    public static void placeShipLocations(int x, int y, int direction) {

        for (int i = 0; i < len; i++) {
            // place in UsedLocations array
            placeInNonEmpty(x, y);
            // place in current shipLocations
            shipLocations[i][0] = x;
            shipLocations[i][1] = y;
            if (direction == 1) {
                // moving location up and down
                y++;
            }
            else {
                // moving location left and right
                x++;
            }
        }
    }

    public static boolean validPlacement(int x, int y, int direction) {

        for (int i = 1; i < len; i++) {
            
            if (direction == 1) {
                // moving location up and down
                y++;
            }
            else {
                // moving location left and right
                x++;
            }
            
            for (int[] loc : usedLocations) {
                // check through each location in usedLocations
                if (loc[0] == x && loc[1] == y) {
                    // moving in that direction is not compatible return false and try again
                    return false;
                }
            }
        }
        // if it makes it through that checking minefield return true.
        return true;
    }

    public static int[][] getUsedLocations() {
        return usedLocations;
    }
}
Jacob Garwin
  • 55
  • 1
  • 9
  • 1
    You're not showing the code for it, but most likely your `ShipLocations.createLocations()` doesn't actually return a new array every time like it should, instead you're sharing the same array between all the ships. – Kayaman Jun 30 '20 at 20:36
  • @Kayaman I don't believe this to be the case. I am setting the locations and then immediately getting right after in the first loop. You can see by the output that the get methods in the first loop return different locations so therefore the .createLocations() has to be creating different locations for those to have been retrieved. – Jacob Garwin Jun 30 '20 at 20:40
  • Do you want to test your belief by showing the code in `ShipLocations`? – Kayaman Jun 30 '20 at 20:41
  • Sure!! @Kayaman (done.) – Jacob Garwin Jun 30 '20 at 20:43
  • Note that in the first loop you set the locations, then print it immediately afterwards. If you print the locations only after the loop is finished instead of inside the loop, you might notice a different output. – Kayaman Jun 30 '20 at 20:45
  • 1
    Well, I was right about the shared array. Instead of all those static member variables, the method should really create a new array (a local variable), and return that. You'll need to modify `placeShipLocations` method too as it is accessing the member variable. You could do that by passing the array as a parameter for example. – Kayaman Jun 30 '20 at 20:56
  • Is this a reference variable issue? So I see that each call to .createLocations() will overwrite the shipLocations member variable since it is a static variable but if you are assigning two different ship object locations at two different times, why are they still connected and being affected by the change in shipLocations? – Jacob Garwin Jun 30 '20 at 21:08
  • I..suppose, depending on what you mean by it. Since you only have one `new int[len][2];`, it means there's only one array. You [pass that array around](https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value), and unlike bacteria it will not divide into new arrays. Any modifications are done on a single array which you have shared between your ships and your `ShipLocations` class. – Kayaman Jun 30 '20 at 21:13
  • 1
    @Kayaman Makes sense. Thank you for the clarification and help. – Jacob Garwin Jun 30 '20 at 21:24

0 Answers0