4

I'm currently writing a grid-based puzzle game, and have run into a problem.

I have an abstract Sprite class where each Sprite represents some object on my map (a player, a wall, etc). This Sprite class has children (Item, Entity, Obstacle), and these children have children (Item has InventoryItem and InstantUseItem), and so on. Only children without any further children are not abstract, so you can only instantiate concrete objects that you can find in-game (you can instantiate Sword and Arrow, but not Weapon - their parent object).

My problem is that I'm storing all the objects on Tiles (each map has width*height Tiles) in a Sprite ArrayList, and now I want to do something like find which Tile the Player object is on, or find all the Tiles that contain Enemy objects (or classes that inherit from them).

The problem is that, as far as I can tell, I can't do something akin to this in my Map or Tile object:

public ArrayList<t> findAllSpritesOfType(Type t) {
    ArrayList<t> sprites = new ArrayList<t>();
    for(Sprite s : this.getSprites()) {
        if(s instanceof t) {
            sprites.add((t) s); //Add after casting to right class
        }
    }
    return sprites;
}

Even if I try to implement a static function in Sprite, I would need this (a kind of 'automatic covariance' amongst all of Sprite's children):

public static ArrayList<this.Type> getSpritesOnTile(Tile t) {
    ArrayList<this.Type> sprites = new ArrayList<this.Type>();
    for(Sprite s : t.getSprites()) {
        if(s instanceof this.Type) {
            sprites.add((this.Type) s); //Add after casting to right class
        }
    }
    return sprites;
}

The other methods I have thought of involve:

  1. Making the latter method return an array of Sprites, then overriding this method in all children to return an array of children using covariance.
  2. Making Sprite contain isWeapon(), isItem(), isEntity(), etc, (which all return false) then override these methods in the appropriate children. I now use these methods instead of a unified isGivenType(Type t) method, or a unified static Type.isGivenType() method.

Making sure my program conforms to Object Oriented Principles, and remains easily extensible, is very important to this project. Is there any way to implement any of my solutions, or achieve my goal?

Jinan Dangor
  • 152
  • 1
  • 7
  • A different approach would've been using an enumeration for your different types of sprite. You could then define how each Item/Entity/Whatever behave in enum specific functions and have various groups of enums by "functionallity" using interfaces. See [link](https://stackoverflow.com/questions/35650045/how-to-enable-enum-inheritance) – Jorge.V Sep 17 '18 at 07:05
  • Does this code actually compile? `ArrayList` looks very suspicious to me. Can you explain why you actually need this functionality? Your question looks like an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Turing85 Sep 17 '18 at 07:05
  • @Jorge.V that only moves the problem. Instead of having the `is...()`-methods, one creates a new `Enum` entry when creating new item-types, which is not ideal either. – Turing85 Sep 17 '18 at 07:06
  • 1
    “*My problem is that I'm storing all the objects … in a Sprite ArrayList*…”—Exactly. And when you stop doing that, the problem will be done… – Holger Sep 17 '18 at 08:15
  • @Turing85 'this.Type' does not compile, it isn't meant to. It's simply meant to illustrate that I'd like some method of getting the Type of the object calling this method, through some means which this post is meant to find. – Jinan Dangor Sep 17 '18 at 08:19

1 Answers1

2

It looks like this is what you want:

public <T extends Sprite> ArrayList<T> findAllSpritesOfType(Class<T> clazz) {
    ArrayList<T> sprites = new ArrayList<>();
    for(Sprite s : this.getSprites()) {
        if(clazz.isInstance(s)) {
            sprites.add((T) s);
        }
    }
    return sprites;
}

And then you can use it like this:

List<Item> items = findAllSpritesOfType(Item.class);

Another way to do it would be to return a stream instead of a list:

public <T extends Sprite> Stream<T> findAllSpritesOfType(Class<T> clazz) {
    return getSprites().stream().filter(clazz::isInstance).map(clazz::cast);
}
Alex - GlassEditor.com
  • 14,957
  • 5
  • 49
  • 49
  • Why did you prefer to return a stream instead of List using collectors? – Aguid Sep 17 '18 at 08:30
  • 2
    @Aguid see this question: https://stackoverflow.com/questions/24676877/should-i-return-a-collection-or-a-stream , maybe they just want to do something like a `count` or `forEach` afterwards so using a stream avoids storing all the elements in a new collection. In the question one of the use cases is find all the tiles that contain Enemy objects, that could be done like this: `tiles.stream().filter(t -> t.findAllSpritesOfType(Enemy.class).findAny().isPresent())` which now doesnt have to look at all of the sprites on the tile, it can stop after the first enemy. – Alex - GlassEditor.com Sep 17 '18 at 08:40