Java enum pairs / "subenum" or what exactly?

| | August 11, 2015

I have an RPG-style Item class and I stored the type of the item in enum (itemType.sword). I want to store subtype too (itemSubtype.long), but I want to express the relation between two data type (sword can be long, short etc. but shield can’t be long or short, only round, tower etc). I know this is wrong source code but similar what I want:

enum type
{
    sword;
}

//not valid code!
enum swordSubtype extends type.sword
{
    short,
    long
}

Question: How can I define this connection between two data type (or more exactly: two value of the data types), what is the most simple and standard way?

  1. Array-like data with all valid (itemType,itemSubtype) enum pairs or (itemType,itemSubtype[]) so more subtype for one type, it would be the best. OK but how can I construct this simplest way?

  2. Special enum with “subenum” set or second level enum or anything else if it does exists

  3. 2 dimensional “canBePairs” array, itemType and itemSubtype dimensions with all type and subtype and boolean elements, “true” means itemType (first dimension) and itemSubtype (second dimension) are okay, “false” means not okay

  4. Other better idea

Thank you very much!

3 Responses to “Java enum pairs / "subenum" or what exactly?”

  1. you can sorta do nested enums:

    enum dogs {
        boxer, collie;
    }
    enum cats {
        siamese, tom
    }
    enum Animal {
        cat(cats.tom), dog(dogs.boxer);
        Animal(Enum e) {
            this.e = e;
        }
        Object[] subValues() {
            return e.getDeclaringClass().getEnumConstants();
        }
        final Enum e;
    }
    public class Main {
        public static void main(String[] args) {
            for (Animal animal : Animal.values()) {
                System.out.print(animal);
                for (Object o : animal.subValues())
                    System.out.print(" " + o);
                System.out.println();
            }
        }
    }
    

    i wrote some code for an ad&d 2.0 game here that has character classes, races, weapons classes and a dice roller that may be useful.

  2. Instead of using enums or making classes for each kind of object, I think the type object pattern is a friendly solution here. In your example, you don’t really show how the enums are being used, so it’s hard to show what a corresponding type object implementation would look like, but it would be something like:

    class ItemType {
      public ItemType(ItemType parent, Integer damage) {
        this.parent = parent;
        this.damage = damage;
      }
    
      public int getDamage() {
        // Here a subtype can just choose to completely override the
        // damage or inherit it. A more complex system could maybe
        // add them or combine them in some other way.
        if (damage != null) return damage;
    
        return parent.getDamage();
      }
    
      private ItemType parent;
      private Integer damage;
    }
    
    class Item {
      public int getDamage() {
        return type.getDamage();
      }
    
      private final ItemType type;
    }
    
    ItemType swordType = new ItemType(null, 4);
    ItemType shortSwordType = new ItemType(swordType, 3);
    // inherits damage
    ItemType longSwordType = new ItemType(swordType, null);
    
    Item sword = new Item(swordType);
    

    Cool stuff about this:

    1. You only ever need to define two Java classes, ItemType and Item.
    2. You can define how subtypes refine their parent types however you want: they can overide values, or stack them, or whatever. You could even support multiple inheritance of you wanted by giving ItemType a list of parent types.
    3. Makes it much easier to make your item types data-driven. You just create new instances of ItemType with different values that you’ve read from some file.

    In the context of games, this is one of my absolute favorite design patterns and one I wish more people knew about. Every time I see a hard-coded class named Sword, I cry a little on the inside.

    You can see a bigger example of this here. There’s no inheritance in ItemType in Amaranth, though, because the data format itself handles that.

    Edit: Responding to tenpn’s comment below.

    The assumption with the type object pattern is that your different “types” of object will vary mostly in attributes or maybe select from a few different pre-defined behaviors. If each type of item really does behave wholly differently, then it may be a better fit to just use full-on classes.

    But, if the types do pull from a set of well-defined behaviors, or you want to define your behavior in data instead of hard-coding it, type objects can still work. In many cases, behavioral differences can be modeled either using different attributes, or through the strategy pattern.

    Answering your use cases specifically:

    What if the shortsword only damages orcs

    class ItemType {
      public ItemType(ItemType parent, Integer damage,
          Map<MonsterType, Float> multipliers) {
        this.parent = parent;
        this.damage = damage;
        this.multipliers = multipliers;
      }
    
      public int getDamage(MonsterType monster) {
        return getBaseDamage() * getMultiplier(monster);
      }
    
      protected int getBaseDamage() {
        if (damage != null) return damage;
        return parent.getDamage();
      }
    
      protected float getMultiplier(MonsterType monsterType) {
        Float multiplier = multipliers.get(monsterType);
        if (multiplier != null) return multiplier;
        if (parent == null) return 1;
        return parent.getMultiplier();
      }
    
      private ItemType parent;
      private Integer damage;
      private Map<MonsterType, Float> multipliers;
    }
    
    MonsterType orc = ...
    ItemType shortSword = new ItemType(null, 123,
        Collections.singletonMap(orc, 0);
    

    Now you’ve got an extensible way to make certain items have different strength against different monsters, and you can still make it data-driven.

    My roguelike does something similar, though it adds a couple of levels of abstraction. An ItemType may have one or more Powers which modify the item (think “Sword of burning” where “of burning” is the power). A Power can have a set of flags. There are flags like “hurts-orcs” that modify the damage against a Group, and then, finally, a MonsterType can be in one or more Groups. The last allows items to be strong against different overlapping sets of monsters.

    For example an undead dragon is in both the “undead” and “dragon” groups, so a weapon that does extra damage against either of those will be effective against the monster.

    What if the dagger does random damage?

    interface Dice {
      int roll();
    }
    
    class FixedDice implements Dice {
      public FixedDice(int value) {
        this.value = value;
      }
    
      public int roll() { return value; }
    
      private int value;
    }
    
    class RangeDice implements Dice {
      public RangeDice(int min, int max) {
        this.min = min;
        this.max = max;
        this.random = new Random();
      }
    
      public int roll() {
        return random.next(max - min) + min;
      }
    
      private int min;
      private int max;
      private Random random;
    }
    
    class ItemType {
      public ItemType(ItemType parent, Dice damage) {
        this.parent = parent;
        this.damage = damage;
      }
    
      public int getDamage() {
        if (damage != null) return damage.roll();
        return parent.getDamage().roll();
      }
    
      private ItemType parent;
      private Dice damage;
    }
    
    ItemType dagger = new ItemType(null, new RangeDice(2, 20));
    

    This is how my roguelike works now. Most attributes on a type are instances of a Roller class that can select from a couple of different number generators. These can be specified in the data files like:

    123  // fixed amount
    2-10 // equally weighted random range
    3d6  // d&d style dice roll
    
  3. This sounds more like you’re trying to simulate a class hierarchy with enums. Just do items -> weapons -> swords -> longsword/shortsword types, with appropriate virtual functions to provide the necessary functionality.

    Using enums would lead to type switches all over your code, which is exactly the problem polymorphism is designed to solve.

Leave a Reply