Focus on item from ArrayList in zty.pe like game


#1

I am in the process of making a zty.pe game, and I have defined classes for own ship, enemies (those meant to be shot with words) as well as a projectile which own ship fires. The Enemy-class item has a string assigned when it is created. When the user presses a key on the keyboard Enemy-class will then react if it corresponds to the first character of the assigned string:

if (outputString.length() >= 1 && key == outputString.charAt(0) {
            manipulString.deleteCharAt(0);
            projectiles.add(new Projectile(location.x, location.y));
            score = score + 1;
        }

This works, however, it is not good enough, as it should only choose the item which is corresponds and is closest.

I am trying to remedy this by making a new ArrayList out of the ArrayList of enemies, where I only include the items with the first character of the word/belonging string.

boolean corresponding() {
      if (outputString.length() >= 1 && key == outputString.charAt(0))
        return true;
    }

I would then like to sort the items after which items have the closest PVector location, i.e. sort after

enemies.location.dist(new PVector(width/2, height-40));

which is the distance to the mothership/own ship. I have searched for ways to sort ArrayLists with PVector, however, it seems to not be applicable in this case.

Gif of how it currently works. Additionally, if two characters repeat, it will remove both of them.

Thank you in advance. Please tell me if this is not understandable. Alternatively, I have linked my GitHub repository where fjender.pde is the Enemy class, skib.pde is the OwnShip class, projektilet.pde is the Projectile class and the main sketch is Spiludvikling.pde.


#2

First off, this is a well formatted question - I appreciate the work you’ve put into this.

However, I don’t know if I completely understand your problem, so I want to clarify:

  • The closest ship needs to be found
  • Only the current “target” ship should have letters removed

Is this right? Assuming it is, let’s address that first problem

Finding The Closest Ship
It looks like you already understand how to find distance:

enemies.location.dist(new PVector(width/2, height-40));

However, while there are a variety of ways to sort these distances, a question I ask is - do you care about any of the distances besides the closest ship? If not, it might be simpler to find the smallest distance, and remember it’s index.

// pseudocode
enemy_distance_list = all enemy distances from mothership

min_index = 0
for ( i < enemy_distance_list.length )
   if ( enemy_distance_list[i] < enemy_distance_list[min_index] )
     min_index = i;

Something like this might work. So the second question is, what do we do with this “closest” ship?

Targeting Only The Closest Ship

I played zty.pe a little and one of the elegant things it does is “lock” you onto a ship until you have destroyed it - this means that even if a different ship is closer, you don’t care about it until the first ship is gone. So maybe you can keep track of the current “target” with a global variable of some sort. The only times you need to search for the closest enemy is whenever the current target is destroyed, or a new wave starts!

Does this help?


#3

Hi tony, thank you for your answer. I think you understood it correctly. However, your interpretation of the locking system is not entirely correct, as you can unlock by pressing backspace — this should not be a huge thing to implement.

Although your pseudocode is helpful, I am still unsure as to how I can do this. Have you seen my GitHub directory? If not, I will try to explain my programme in further detail:
My Enemy-class consists of PVectors for velocity, location and targetLocation. The enemies are in ArrayList<Enemy> enemies = new ArrayList<Enemy>();. Can I just sort these by making a list?


#4

I was thinking you could iterate through enemies and make a temporary array of floats, lets say, enemy_distances. It would be an unsorted array. From there, you would find the smallest ‘distance’ just by iterating through the entire array with that pseudocode I wrote above.

Now the key is that you would be searching for the smallest distance, but remembering just the index of it. This way, that index you found would correspond to the index of the enemy with that shortest distance.

Does that make sense?


#5

Hi Tony, it does. I am not fully aware as to how I have to code it, I have come up with the following:

void sortering () {
  int indexFlag = 0;
  for (Enemy before : enemies) {
    indexFlag = indexOf(before);
    for (Enemy inProcess : enemies) {
      if (before != inProcess) {
        if (inProcess.distance < before.distance && indexOf(inProcess) > indexFlag && before.corresponding) {
          int indexinProcess = indexOf(inProcess);
          int indexbefore = indexOf(before);
          enemies.set(indexinProcess, before);
          enemies.set(indexbefore, inProcess);
        }
      }
    }
  }
}

However, it does not work (not surprisingly, as I am not entirely sure about how to do this). I want to sort, where I have an indexFlag (standpoint), a for-loop which goes through the different enemies. before should be the first to initially be what is at enemies[0], and indexFlag should be set to be the indexOf(before). Then it should loop through different enemies (inProcess) if before is not the same as inProcess (before and inProcess are items from enemies). If inProcess’ distance is less than that of before as well as having index after indexFlag, it (inProcess) should switch places with before. This will then continue until it is correctly sorted.

Do you think this (not the code, the idea) will work? And do you have the capacity to help with rewriting the code?

Thank you.


#6

Lets start by making a float array. How long should this be? As long as enemies ArrayList.

float[] distances = new float[enemies.length];

Now, lets load up our array:

for(int i = 0; i < enemies.length; i++)
{
   distances[i] = enemies[i].location.dist(new PVector(width/2, height-40));
}

Finally, lets, find the index of the smallest distance:

int mindex = 0; // This should be a global variable

for(int i = 0; i < enemies.length; i++)
{
   if( distances[i] < distances[mindex] )
   {
      mindex = i;
   }
}

At this point, we can use mindex to find the enemy with the closest distance.
As a function, this might look like:

int getIndexOfClosestEnemy()
{
   int mindex = 0; // This should be a global variable
   float[] distances = new float[enemies.length];
   for(int i = 0; i < enemies.length; i++)
   {
      distances[i] = enemies[i].location.dist(new PVector(width/2, height-40));
   }
   
   for(int i = 0; i < enemies.length; i++)
   {
      if( distances[i] < distances[mindex] )
      {
         mindex = i;
      }
   }

   return mindex;
}

Hope this helps :call_me_hand: (Note: I didn’t test any of this in the IDE so it probably needs to be debugged)


#7

Thank you so much tony, that was very helpful. As my enemies is an ArrayList and not just a list I have changed the code a bit. In my drawEnemy method, where I loop through my enemies ArrayList, I suppose I should implement our sorting mechanism by saying something like

if (i == mindex) {
        manipulString.deleteCharAt(0);
        projectiles.add(new Projectile(location.x, location.y));
        score = score + 1;
}   

The sorting algorithm now looks like this:

void sortering () {
  float[] distances = new float[enemies.size()];

  for (int i = 0; i < enemies.size(); i++)
  {
     distances[i] = enemies.get(i).location.dist(new PVector(width/2, height-40));
  }

  for (int i = 0; i < enemies.size() - 1; i++)
  {
     if ( distances[i] < distances[mindex] )
     {
        mindex = i;
     }
  }
}

int getIndexOfClosestEnemy () {
   int mindex = 0; // This should be a global variable
   float[] distances = new float[enemies.size()];
   for (int i = 0; i < enemies.size(); i++)
   {
      distances[i] = enemies.get(i).location.dist(new PVector(width/2, height-40));
   }

   for(int i = 0; i < enemies.size(); i++)
   {
      if( distances[i] < distances[mindex] && enemies.get(i).corresponding)
      {
         mindex = i;
      }
   }
   return mindex;
}

However, when I try to use it

void drawEnemy() {
  for (int i = enemies.size()-1; i >= 0; i--) {
    Enemy enemy = (Enemy) enemies.get(i);
    enemy.informationProjektil();
    sortering();
    getIndexOfClosestEnemy();
    if (i == mindex && keyPressed && enemy.corresponding && key == enemy.manipulString.charAt(0) && enemy.manipulString.length() > 0) {
          enemy.manipulString.deleteCharAt(0);
          projectiles.add(new Projectile(enemy.location.x, enemy.location.y));
          score = score + 1;
    }
    if (enemy.dead())
        enemies.remove(i);
    enemy.update();
    enemy.display();
    if (enemy.location.x < width / 2 + 20 && enemy.location.x > width / 2 - 20 && enemy.location.y > height - 60 && enemy.location.y < height - 20)
        end = true;
  }
}

I get this error: fjender.pde:103:0:103:0: StringIndexOutOfBoundsException: String index out of range: 0. Line 103 is where it says if (i == mindex && keyPressed && enemy.corresponding ... )
I do not understand why, as I think I have ensured that it shouldn’t happen. Other than that, the algorithm works. My manipulString is a StringBuilder object.

EDIT: it only focuses on the nearest. It should focus on the nearest corresponding enemy.

BIG UPDATE:
So I fixed the major error with StringIndexOutOfBoundsException by making a new if-statement instead of incorporating it in an already existing one. However, I have got two problems (the latter being somewhat unrelated to the topic):

  1. It only focuses on the nearest enemy. It does not focus on the nearest enemy that corresponds.
  2. If a word has repeating characters it will actually delete both of them, whereas it should only delete one. Please see the image:

#8

What does this line really do?

    if (i == mindex && keyPressed && enemy.corresponding && key == enemy.manipulString.charAt(0) && enemy.manipulString.length() > 0) {

What is mindex telling you?


#9

mindex tells me at what index in the enemies ArrayList the enemy with the smallest distance is positioned. The line checks whether an enemy is closest, whether or not a key has been pressed and if the enemy corresponds. The last to conditionals are not relevant.


#10

Oh, I didn’t see that second conditional in getIndexOfClosestEnemy().

Is drawEnemy() run every frame? If so, getIndexOfClosestEnemy() is going to be called 60 times a second, and in just the first frame, your closest enemy is going to found, decremented, and “disqualified”.

This is a guess, but to address this, perhaps have a global Enemy target, and check only target every draw() loop, unless target is destroyed or you switch to a different target. This might require some code restructuring.


#11

Alright @tony, I am not entirely sure about what you are saying. drawEnemy() is drawn every frame, but what do you mean by it being “found, decremented, and “disqualified””?
I don’t think that targetting is really important. I just want it to shoot/select the closest corresponding enemy (that is, where keyPressed && key == enemy.manipulString.charAt(0) evaluates to true). I have tried a lot of ways to implement this, but it either fails miserably and messes up the whole thing or just doesn’t work. I think I need to change the sorting algorithm somewhat, to also accommodate whether enemies.get(mindex).corresponding == true. This is just a minor issue though.
Largely thanks to you my game works quite well:


#12

The reason I suggested using a target is because, 60 times a second, you are iterating through every single enemy in your list.

As you said yourself, all you care about is the

In addition, having a target would address that other bug:

The reason (I think) this problem is happening, is due to the fact that you’re going through the entire enemy list every frame. It is very hard to hold down a key for a single frame, meaning that if you press ‘a’, it’s going to be detected for multiple frames.

Take this example:

  • You have two enemies, APPLE and ANT
  • You press ‘a’, which is detected for at least 2 frames

Given that enemyDrawn() is called every frame:

Frame 01 -> APPLE is detected, targeted, and the first A is deleted. PPLE is left
Frame 02 -> PPLE does not correspond to the current key ‘a’, so the next valid enemy is ANT. ANT is targeted, A is removed, NT is left.

This is all executed in only two frames. To the viewer, it looks like our program is buggy and deleting all the enemies with A in the front!

I hope this helps. As it currently is, the game looks great!


#13

Oops @tony, apparently I forgot to tell you that I have already solved problem 2:

by putting keyPressed = false; at the end of the draw-loop.

As making targets would require a major reprogramming of the whole thing, I would like to avoid that. It is not a major issue though.

I am trying to use audio, put I keep getting errors:

sep. 17, 2018 10:14:32 AM com.jsyn.devices.javasound.JavaSoundAudioDevice 
INFO: JSyn: default output latency set to 80 msec for Windows 10
sep. 17, 2018 10:14:32 AM com.jsyn.engine.SynthesisEngine start
INFO: Pure Java JSyn from www.softsynth.com, rate = 44100, RT, V16.8.0 (build 463, 2017-10-16)
Spiludvikling.pde:18:0:18:0: ArrayIndexOutOfBoundsException

where Spiludvikling.pde:18:0:18:0 is

    eksplosionsLyd = new SoundFile(this, "eksplosion.mp3");

And I have installed library Sound.

import processing.sound.*;
SoundFile eksplosionsLyd;


    eksplosionsLyd = new SoundFile(this, "eksplosion.mp3");

...

        eksplosionsLyd.play();
        eksplosionsLyd.amp(1);

The soundfile exists. Could this be a problem with the library/incompability?


#14

I have solved the problem by using Minim.


#15

This was a very interesting discussion – and I love the game project.

My understanding of zty.pe’s opening levels is that it constrains the enemy set so that, when new enemies are spawned, the new enemy always has a unique first letter. There are never two enemies beginning with ‘a’ on the screen at the same time, so the player doesn’t need to think about proximity – whatever word they are looking at and begin typing always works, as if by magic (although this magic can never have more than 26 labeled enemies on screen).

If a word is partially consumed and then unlocked with backspace – say, ‘basis’->‘sis’ – and another whole word is available – say ‘shy’ – then one way to choose is proximity. However one could also choose the first spawned, or the last touched, or the shortest or the most consumed (with tiebreaker).

I personally would probably ditch the backspace unlocking option. It adds a lot of complexity and I’m not sure why it is useful for gameplay.