Battleship in java

Started by
17 comments, last by rip-off 9 years, 8 months ago

I'm not familiar with Java, so I just googled this and haven't tested it, but you can make some optimizations like this:

Instead of


public static void arrayAssignInput(int[][] a, char ch, int ir)
	{

			if(ch == 'A' || ch == 'a')
				a[ir][1] = 1;
			if(ch == 'B' || ch == 'b')
				a[ir][2] = 1;
			if(ch == 'C' || ch == 'c')
				a[ir][3] = 1;
			if(ch == 'D' || ch == 'd')
				a[ir][4] = 1;
			if(ch == 'E' || ch == 'e')
				a[ir][5] = 1;
			if(ch == 'F' || ch == 'f')
				a[ir][6] = 1;
			if(ch == 'G' || ch == 'g')
				a[ir][7] = 1;
			if(ch == 'H' || ch == 'h')
				a[ir][8] = 1;
			if(ch == 'I' || ch == 'i')
				a[ir][9] = 1;
			if(ch == 'J' || ch == 'j')
				a[ir][10] = 1;

	}

use


public static void arrayAssignInput(int[][] a, char ch, int ir)
{
    char upper = Character.toUpperCase(ch)
    int val = (int)upper - (int)'A' + 1;
    a[ir][val] = 1;
}
Advertisement


Yeah I know to use descriptive variable names I just didn't think I was going to be showing this to anyone, I will change.

The descriptive names aren't so that it looks pretty when you show it to other people. They are there so that in a year or twos time when you have chopped and changed and refactored your code and added or removed new features you can tell what the variables do. This may only be a small project but, it is good to get into the practice now.


Well Glass_Knife it shouldn't have been that hard to know what rowf meant since I had a comment right in front of it explaining what it meant.

We shouldn't have to scroll to a variable declaration to be able to know what it does. In fact you should never need to comment a variable declaration. If you do then the name you have chosen is not good enough.

All that being said the obvious things to do would be use constants to get rid of the magic number , Split it up into methods. Use descriptive names for your variables and methods (check and check1 are meaningless). Also consider creating a seperate Board class.
After all thats been said ignore any comments about it being a coding horror. I have seen much worse code from so called proffesional programmers. I haven't run it but, I assume that it works and it behaves how you expect it to behave.


I put everything in methods
Nono. You put everything in a method. We say, put separate things in methods. Notice the difference.

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

My journals: dustArtemis ECS framework and Making a Terrain Generator

I don't want to argue semantics with people, I just want to know how to optimize my code.

I don't want to argue semantics with people, I just want to know how to optimize my code.

1. Understand the more you learn about programming, the harder it gets. Frustration is good. It means you are learning. If you expect to master programming this month you will disappointed.

2. You are not trying to optimize your code, but are trying to refactor the code. The two are not mutually exclusive, but you are not replacing you algorithms to improve the speed or reduce the memory usage, you just want to make the code easier to read without changing the behavior. That's refactoring.

Watch this:

I think, therefore I am. I think? - "George Carlin"
My Website: Indie Game Programming

My Twitter: https://twitter.com/indieprogram

My Book: http://amzn.com/1305076532

I don't want to argue semantics with people, I just want to know how to optimize my code.

He was not arguing semantics, he was informing you that you didn't really do what was suggested to you.

Doing what has been suggested, breaking down specific tasks into separate methods, you should be able to end up with a main method that looks similar to rip-offs main. This will help with readability and maintainability and will give you specific, contained areas of code to work with to optimize.

Hi

Create classes,

Call only the processes you need at a function not all.

But mostly rip-off gave you very important anwsers.

HyperV

Yeah I know to use descriptive variable names I just didn't think I was going to be showing this to anyone, I will change.

I would still use descriptive names and make it a habit. In 2-3 years when you revisit this code, (even if you think you never will, trust me you will) you will be able to quickly figure out what you were doing because you won't have to guess what variable names were supposed to represent.

Having studied your code a bit more, there are a few more points I would make:

The method names like arrayAssignShip and arrayAssignInput are very mechanical, they describe what they do rather than the intent behind them. The first might be called "placeShip", the other is a little more tricky (I'll talk about this later), but in it's current state perhaps something like "mapInputToBoard". Now, play is a far better name, but it is still a bit vague. A better name might be something like "takeTurn" or "makeMove".

And while you've improved some of the names, the vast majority of them are unchanged. Names like the following are still very poor:

for(Ship e: playerOneShips)
    arrayAssignShip(e, playerOneBoard);
 
// ...
 
int i = 0;
int j = 0;
int k = 0;
int l = 0;
int m = 0;
int b = 0;
char c;
int inrow;
 
// ...
 

for(Ship x : playerShips)
{
    // ...
}

The methods you split up are still quite large. This means that they are still difficult to understand. The shortest one, arrayAssignInput, is about as long as I ideally let a method get. This way, you can see all of a method without scrolling. Shorter methods again tend to be more "obvious", and are easier to give good names as they are clearly focussed on one task.

You should try to separate display from your internal data structures. For example, all your arrays have 11 elements, but you're using elements 1 through 10. This is convenient as the indices you are using and the inputs you accept from the user. However, this is highly unconventional. While it isn't particularly problematic for the numeric input, it starts getting very inconvenient indeed for the character input. Instead, focus on quickly validating the user input and converting it to an internal format. In this case, the number the user inputs should be validated as between 1 and 10, and then you subtract one and use as your row index. Likewise, your character input should be validated for valid range and converted to an index, as LennyLen indicated how to do.

This is most obvious in the difficulty you appear to have handling the columns of the board state and the ship - by handling this as character based indexes internally, it certainly complicates a lot of things, even though it would be technically possible to achieve.

By elevating the concept of a "point", "location" or "co-ordinate" to something that can be described in the vocabulary of your program,

Related to how you represent indices is how you represent ships and guesses. This is structuring your data in a way that is convenient for the program. There are often multiple ways to do so.

For instance, instead of an array of 1s and 0s (which probably should have been booleans), you can represent the board as a grid of Ship references. So if you have a patrol boat at the top left horizontally, those two occupied grid entries could point back to this Ship. This allows you to quickly get from a grid location to the Ship that cross it, so you can call methods to mark the ship as being hit.

In another representation, you could potentially dispense with the Ship objects themselves - all you need is something like a ShipType enumeration, with an entry for each Ship type, and an be used to get the display name and length of the ship. Your grid could consist of ShipType values of the appropriate length. Once you know a ship has been hit, you can attempt to access the adjacent horizontal or vertical entries in the grid to determine if it has been sunk.

Yet another way is to dispense with the grid of ship locations. Instead, your Ship class can each contain an array of booleans the length of that ship, each element of which represents whether that location has been hit. So to resolve if a hit has occurred, just iterate through the Ships, determine if the grid location lies between the locations spanned by the ship, and if so calculate the index into that array and mark it as hit.

Currently, you're storing redundant information - you store what you call a "count", which appears to be the number of hits that player has scored. You can also calculate this on demand by iterating though the Ships and summing the "hits taken" of each one. This isn't always a bad idea, in fact sometimes it can make sense to do so - when it might take a long time to iterate through all the elements in question. Here, it isn't going to take any time at all. However, it can cause difficulty as the values can get out of sync, though this isn't likely in your example, despite the fact that you've actually made this more complex still as you have the count arrays, and also more count variables.

I mentioned about your guess representation and algorithm. It is very complex. Part of the problem is that you're mapping grid co-ordinates to a separate grid, and then looping simultaneously between this array and your other arrays. Instead, try to structure the algorithm to avoid the temporary array and instead access the array of interest directly.

Some more general notes that are more purely stylistic:

I'd recommend scoping your variables much more tightly. For example, loop counters are best declared inside the loop statement, like so:

for(int i = 1; i <= 10; i++)
{
    for(int j = 1; j <= 10; j++)
    {
        if(playerGuess[i][j] == 3)
            System.out.print("* ");
        if(playerGuess[i][j] == 1)
            System.out.print("X ");
        if(playerGuess[i][j] == 0)
            System.out.print(playerGuess[i][j] + " "); 
    }
 
    System.out.print("\n");
}

Another example is here:

System.out.print("\nEnter a charactor from A - k for column and and number from 1 - 10 \nrespectively for the quardents of your shot: ");
c = in.next().charAt(0);
inrow  = in.nextInt();

These variables should be declared at this point, like so:

System.out.print("\nEnter a charactor from A - k for column and and number from 1 - 10 \nrespectively for the quardents of your shot: ");
char c = in.next().charAt(0);
int inrow  = in.nextInt();

Encapsulation is capturing logic inside a class and not having it leak outside. Ideally, all variables in all classes will be "private". Not only that, but the details of this state, even if private, should not be exposed lightly. Here is an example:

ship.setHit();
 
// ...
 
if(ship.getHit() == ship.getSize())
    System.out.print("you sunk my : " + ship.getName() + "\n");
else
    System.out.print("\n\nPlayer hit a ship\n");

Here, we can hit the "hit" value by having more meaningful method names that are not necessarily related to the variables that are used by them:

ship.markDamaged();
 
// ...
 
if(ship.isSunk())
    System.out.print("you sunk my : " + ship.getName() + "\n");
else
    System.out.print("\n\nPlayer hit a ship\n");
 
// ...
 
class Ship {
     
     private final int length;
     
     private int hitsTaken;
 
     // ...
 
     public void markDamaged() {
         assert hitsTaken < length;
         hitsTaken += 1;
     }
     
     public boolean isSunk() {
         return hitsTaken >= length;
     }
}

I've already mentioned about the magic numbers, but it bears repeating. It is very difficult to understand code where you see strange constants like 3 or 5 that have no meaning in an of themselves. You have to reason about the consequences before you can discover the meaning, and it can be easy to make an incorrect guess if you make a mistake or are impatient. Far better to name constants like 17 as "HitsRequiredToWin".

If you're only concerned about 0 or 1, using a boolean is clearer. If you're not dealing with numeric data, then consider using an enumeration.

I'd recommend moving your Ship class, and any other classes you make, to a separate file.

This topic is closed to new replies.

Advertisement