Java: Optimizing algorithm in game - java

I'm creating a game (Java) with scene2d.
I wrote function for collision detection but I think it's bad function. It looks bad.
How can I optimize it? Make faster and more beautiful.
private void deleteEnemies()
{
for(int i = 0; i < getActors().size - 1; i++)
{
if(getActors().get(i) != null && getActors().get(i) instanceof Enemy)
{
////////////////
for (int j = 0; j < getActors().size - 1; j++)
{
if(getActors().get(j) != null && getActors().get(j) instanceof Ball)
{
if (actorsIntersecting(getActors().get(i), getActors().get(j)))
{
getActors().get(i).remove();
getActors().get(j).remove();
}
}
}
//////////////
}
}
}

Put getActors().get(i) in a variable, dont call it twice in the outer if
Same for getActors().get(j) in the inner if
use these variable in the most inner if's condition and body
save the size in a variable because now the .size function is being called on every iteration when the for condition is checked
You shouldn't use a size that can dynamically change during the loop for the loop condition (because you are removing items as you go) which brings us back to #4.
Other than that its pretty much ok coding style perspective and I doubt you can make it more efficient than with what I told you (Other than using threads)

Since you will do this frequently, consider storing the Enemies and Balls in their own structures (List or Set or whatever works). That prevents you from looping through actors you don't need, and avoids the instanceof checks.

Well, my first idea was to check only "nearest" enemies and not all of them. Somehow try to decrease size of that list.
2. Second one - please check your and conditions in and one by one - now you are checking 2 conditions always. Try to put "heavier" if later, for example:
from:
if(getActors().get(i) != null && getActors().get(i) instanceof Enemy)
to:
if(getActors().get(i) != null) {
if(getActors().get(i) instanceof Enemy) {
.....
}
}
3. call your getActors().get(i) one time - save to variable.
4. I'm thinking why is it necessary to check if an actor is null, maybe just remove nulls from list or keep uninitialized actors on another list. Also try this with Balls and Enemies, please don't keep every actor on a single list.

I would rewrite the models a bit, so they can test the intersection itself and then do the delete like that (probably it can still be improved)
private void deleteEnemies () {
List<Actor> actors = getActors();
List<Actor> toRemove = new ArrayList<Actor>();
int actorsSize = actors.size();
Actor first = null, second = null;
for(int i = 0; i < actorsSize; ++i) {
first = actors.get(i);
for(int j = 0; j < actorsSize; ++j) {
if(i == j) continue;
second = actors.get(j);
if(first.intersects(second)) {
toRemove.add(first);
toRemove.add(second);
}
}
}
actors.removeAll(toRemove);
}

Don't use size(), define a variable
Try not to cast. Try not to uae instanceof.
Maybe, sort lists by zsort or the like so u can, sometimes, start and or stop the loops sooner??

Adding to the (very good) suggestions of the other participant: cache the enemies and projectiles in separate structures, so you don't have to check what they are at all.
Use the time vs space trade-off as much as you can: the standard approach, as hinted by Tomek, in this kind of situations is to reduce the number of checks (=iterations) by pruning the enemies and projectiles that cannot possibly collide within the current frame (they are way to far).
Anyway, a word of advice: go on with the game, complete as much as you can so that it will run correctly (if slowly), and only then go for the optimization.
That because
by optimizing preemptively in this way you will never finish it
you don't know how the final game really will be, perhaps: maybe after finishing 90% of it, you will see some easy chances for optimization.

As others have said, the real improvement to speed would be two collections, one with balls and the other with enemies. As for making it look nicer, you could something like this:
for (Actor enemy : getActors()) {
if (enemy != null && enemy instanceof Enemy) {
for (Actor ball : getActors()) {
if (ball != null && ball instanceof Ball && actorsIntersecting(enemy, ball)) {
ball.remove();
enemy.remove();
}
}
}
}

Related

incrementing an object in a 2d array

Good day, so I intend for my code to loop through my array and increment the row index of object by 1 position. I used timer task because I want the object to move forward after certain amount of time. This is the code I have tried. I have looked but I have struggled to find solution relevant to my problem. Would appreciate the help.
class cat_function extends TimerTask {
public void run() {
synchronized (game.board) {
for (int i = 0; i < game.board.length; i++) {
for (int k = 0; k < game.board[0].length; k++) {
if (game.board[i][k] instanceof cat) {
cat garfield = new cat(0, 0);
game.board[i][k] = garfield;
game.board[i][k + 1] = garfield;
}
}
}
}
}
}
Assuming:
game.board is defined as a Cat[][]
an empty cell's value is null
Then all you have to do is
if (game.board[i][k] instanceof cat) {
game.board[i][k + 1] = game.board[i][k]; // Put cat in new location
game.board[i][k] = null; // Remove cat from previous location
}
However, this code still has two problems
What do you do when you reach the edge of the board. You'll have to add logic to make it do something different so you don't fall of the edge.
There's no need to scan the entire game board every time just to find the Cat. Keep the cat's location (indexes) separately so you always know where it is and don't have to look for it.
If there can be more than one cat on the board you will also need logic to decide what happens if two cats "collide" when moving (i.e. you try to move a cat into a cell that already contains a cat).
Solving those problems is left as an exercise for you.

Simple flood fill method causes StackOverFlow error

My flood fill method:
public void fillNeighbours(int x, int y) {
for(int i = -1; i < 2; i++) {
for(int j = -1; j < 2; j++) {
try {
visible[x+i][y+j] = true;
if(num[x+i][y+j] == 0) {
fillNeighbours(x+i, y+j);
}
} catch (ArrayIndexOutOfBoundsException ignored) {}
}
}
}
That catch (ArrayIndexOutOfBoundsException ignored) {} is there for avoiding x and/or y position from going outside of the array. Array size is 30 by 30. I'm making minesweeper like game. So you may know why I need this method and how it should work. If u don't know what minesweeper is then here is quick video about that game: Introduction to minesweeper
The code revisits fields which are set to visible already.
Try something like
if(!visible[x+i][y+j]){
visible[x+i][y+j] = true;
if(num[x+i][y+j] == 0) {
fillNeighbours(x+i, y+j);
}
}
It looks like you're recursively calling fillNeighbours without any break out clause (base case) so the calls fill the stack.
From
Wikistack
The tree laws of recursion are
A recursive algorithm must have a base case.
A recursive algorithm
must change its state and move toward the base case.
A recursive
algorithm must call itself, recursively.
Once fillNeighbours finds a cell and calls itself, the next loop will always call another when i and j are equal to zero. So it will never exit, and crash once the stack is full.
Aside from that, it will produce a very deep tree as it is not keeping track of which cells have been recursed, and will call fillNeighbours on the same cell multiple times.

Spawn random objects without overlapping (Java)?

I'm developing a game in Java, and part of it requires that objects spawn at the top of the screen and proceed to fall down. I have three objects that can possibly spawn, and three possible x coordinates for them to spawn at, all stored in an array called xCoordinate[].
One of the objects is of a class called Enemy, which inherits a class I have called FallingThings. In the FallingThings class, I have methods to generate new objects, my enemy method is below:
public static void generateNewEnemy() {
xIndexEnemyOld = xIndexEnemy;
xIndexEnemy = new Random().nextInt(3);
if (delayTimer == 0) {
while (xIndexEnemy == xIndexEnemyOld) {
xIndexEnemy = new Random().nextInt(3);
}
}
if (xIndexEnemy != xIndexMoney && xIndexEnemy != xIndexFriend) {
Enemy enemy = new Enemy(xCoordinates[xIndexEnemy]);
enemies.add((Enemy) enemy);
} else {
generateNewEnemy();
}
}
xIndexEnemy represents the index of the xCoordinates array.
xIndexMoney and xIndexFriend are the indexes of the xCoordinates array for the two other objects (the comparisons with these values ensures that one object does not spawn directly on top of another).
The delayTimer variable represents the random delay between when new objects spawn, which was set earlier in my main class.
I store each instance of an Enemy object in an ArrayList.
Everything works except for the fact that sometimes, an object will spawn over itself (for example, the delay is 0, so two enemy objects spawn directly on top of each other, and proceed to fall down at the same speed at the same time).
I've been trying to crack this for the past two days, but I understand exactly why my code right now isn't working properly. I even tried implementing collision detection to check if another object already exists in the space, but that didn't work either.
I would be extremely grateful for any suggestions and ideas.
EDIT2
It seems that you still don't understand the problem with your function. It was addressed in the other answer but I'll try to make it more clear.
public static void generateNewEnemy() {
xIndexEnemyOld = xIndexEnemy;
This is just wrong. You can't set the Old index without having actually used a new index yet.
xIndexEnemy = new Random().nextInt(3);
if (delayTimer == 0) {
while (xIndexEnemy == xIndexEnemyOld) {
xIndexEnemy = new Random().nextInt(3);
}
}
This is actually ok. You're generating an index until you get one that is different. It may not be the most elegant of solutions but it does the job.
if (xIndexEnemy != xIndexMoney && xIndexEnemy != xIndexFriend) {
Enemy enemy = new Enemy(xCoordinates[xIndexEnemy]);
enemies.add((Enemy) enemy);
} else {
generateNewEnemy();
}
}
This is your problem (along with setting the Old index back there). Not only do you have to generate an index thats different from the Old index, it must also be different from IndexMoney and IndexFriend.
Now, what happens if, for example, IndexOld = 0, IndexMoney = 1 and IndexFriend = 2? You have to generate an index that's different from 0, so you get (again, for instance) 1. IndexMoney is 1 too, so the condition will fail and you do a recursive call. (Why do you even have a recursive call?)
OldIndex was 0, and now in the next call you're setting it to 1. So IndexOld = 1, IndexMoney = 1 and IndexFriend = 2. Do you see the problem now? The overlapped index is now wrong. And the new index can only be 0 no matter how many recursive calls it takes.
You're shooting yourself in the foot more than once. The recursive call does not result in an infinite loop (stack overflow actually) because you're changing the Old index. (Which, again is in the wrong place)
That if condition is making it so the newly generated index cannot overlap ANY of the previous indexes. From what you said before it's not what you want.
You can simplify your function like this,
public static void generateNewEnemy() {
xIndexEnemy = new Random().nextInt(3);
if (delayTimer == 0) {
while (xIndexEnemy == xIndexEnemyOld) {
xIndexEnemy = new Random().nextInt(3);
}
}
Enemy enemy = new Enemy(xCoordinates[xIndexEnemy]);
enemies.add((Enemy) enemy);
xIndexEnemyOld = xIndexEnemy;
// Now that you used the new index you can store it as the Old one
}
Will it work? It will certainly avoid overlapping when the delayTimer is 0 but I don't know the rest of your code (nor do I want to) and what do you do. It's you who should know.
About my suggestions, they were alternatives for how to generate the index you wanted. I was assuming you would know how to fit them in your code, but you're still free to try them after you've fixed the actual problem.
Original Answer
Here's one suggestion.
One thing you could do is to have these enemies "borrow" elements from the array. Say you have an array,
ArrayList< Float > coordinates = new ArrayList< Float >();
// Add the coordinates you want ...
You can select one of the indexes as you're doing, but use the maximum size of the array instead and then remove the element that you choose. By doing that you are removing one of the index options.
int nextIndex = new Random().nextInt( coordinates.size() );
float xCoordinate = coordinates.get( nextIndex );
coordinates.remove( nextIndex ); // Remove the coordinate
Later, when you're done with the value (say, when enough time has passed, or the enemy dies) you can put it back into the array.
coordinates.add( xCoordinate );
Now the value is available again and you don't have to bother with checking indexes.
Well, this is the general idea for my suggestion. You will have to adapt it to make it work the way you need, specifically when you place the value back into the array as I don't know where in your code you can do that.
EDIT:
Another alternative is, you keep the array that you previously had. No need to remove values from it or anything.
When you want to get a new coordinate create an extra array with only the values that are available, that is the values that won't overlap other objects.
...
if (delayTimer == 0) {
ArrayList< Integer > availableIndexes = new ArrayList< Integer >();
for ( int i = 0; i < 3; ++i ) {
if ( i != xIndexEnemyOld ) {
availableIndexes.add( i );
}
}
int selectedIndex = new Random().nextInt( availableIndexes.size() );
xIndexEnemy = availableIndexes.get( selectedIndex );
}
// Else no need to use the array
else {
xIndexEnemy = new Random().nextInt( 3 );
}
...
And now you're sure that the index you're getting should be different, so no need to check if it overlaps.
The downside is that you have to create this extra array, but it makes your conditions simpler.
(I'm keeping the "new Random()" from your code but other answers/comments refer that you should use a single instance, remember that)
As I see, if delay == 0 all is good, but if not, you have a chance to generate new enemy with the same index. Maybe you want to call return; if delayTimer != 0?
UPDATED
Look what you have in such case:
OldEnemyIndex = 1
NewEnemyIndex = random(3) -> 1
DelayTimer = 2
Then you do not pass to your if statement, then in the next if all is ok, if your enemy has no the same index with money or something else, so you create new enemy with the same index as previous

My Java code wont spawn the next wave?

So I am making a game where there are waves of enemies. The Wave class contains an update method that updates all the enemies in an arraylist of enemies contained in the Wave class. The Wave class also has a boolean called beat that decides whether or not the player has beaten the current wave. I am now have been trying however to start the next wave after the player beats the first. All waves in the arraylist start out with their beat variable as true except for the first. There are currently only two waves. I do not know why this is not working. Thank You for any help.
for(int i = 0; i < 1;i++)
{
if(!w.get(i).beat)
w.get(i).update(g2d);
else if(w.get(i).beat)
{
if(i-1 != -1)
{
if(w.get(i-1).beat && w.get(i).beat)
{
w.get(i).beat = false;
}
}
}
}
Your loop will increment i to the next wave after setting the current wave's beat setting to false, and miss calling the update method for that case. It looks like you should either call its update method immediately after setting beat = false, or perform the if test in the opposite order like this:
for(int i = 0; i < numWaves;i++) // upper range should be the number of waves
{
if(w.get(i).beat)
{
if(i>0) // this can be simplified to "if (i>0)"
{
if(w.get(i-1).beat) // no need to check w.get(i).beat here
{
w.get(i).beat = false;
}
}
}
else
w.get(i).update(g2d);
}
I don't know why you'd initialize a wave's beat state to true then set it to false when its turn comes. Why not just initialize all to false since they really haven't been beat yet?
I'm not sure that I understand your code but I can tell you 2 things. First of all, your loop never loops because as soon as the index is 1, it ends without executing the code a second time. Secondly
if(i-1 != -1)
{
if(w.get(i-1).beat && w.get(i).beat)
{
w.get(i).beat = false;
}
}
is always false due to what I said.

A collision check method works on a mac, but not on PC

So I am stumped. Here is my collision check method`
public void checkCollision ()
{
for (int i = 0; i < bullets.size()-1; i ++)
{
for (int j = 0; j < enemiesLaunched.size()-1; j++)
{
Rectangle temp = enemiesLaunched.get(j).getRectangle();
Rectangle temp2 = bullets.get(i).getRectangle();
`
if (temp2.intersects (temp))
{
String str = bullets.get(i).getPath();
// since the bullets are selective, the following code is to check
// if the right bullets hit the right germs
if (str.equals("oil gland.png")) // bullet is from oil gland
{
if (enemiesLaunched.get(j).getInfo().equals("highAcid"))
{
enemiesLaunched.get(j).setVisible(false);
bullets.remove(i);
}
}
else if (str.equals ("sweat gland.png"))
{
if (enemiesLaunched.get(j).getInfo().equals("lysozome"))
{
enemiesLaunched.get(j).setVisible(false);
bullets.remove(i);
}
}
else
{
if (enemiesLaunched.get(j).getInfo().equals("mucus"))
{
enemiesLaunched.get(j).setVisible(false);
bullets.remove(i);
}
}
`
On my mac, it works exactly how I intended. However, on my PC, it does not. To make matters more baffling, I have implemented the same logic on games further along in the game, and it works just fine on both the mac and pc, any help would be greatly appreciated!
How are you doing your time delta, and what is the velocity on the two objects? If your time delta is sufficiently large enough, you might not detect the collision as the two objects could have pass right through each other between checks. Have a look here for an explaination.
What tears attention is size()-1 - sure? But bullets.remove(i); certainly should be followed by --i; as otherwise the for-incrementing would skip the next bullet.
Optimized it would be by keeping get(i) and get(j) in their own variables.
I'd rather use for-loops like this if possible to ensure I don't have some wrong indexes due to typos or something:
List<Enemy> enemies = new ArrayList<Enemy>;
for (Enemy enemy : enemies) {
...
}
For example with this loop:
for (int i = 0; i < enemies.size()-1; ++i)
you will always leave the last "enemy" untouched.
And then, to be sure I'm not screwing up my Lists and iterations I would keep references to objects that need to be removed and would remove them afterwards, because I'm not sure what happens when removeing items from a collection while iterating over the same collection. The behaviour might be collectiontype and implementation (of the collection) specific.

Categories

Resources