5
\$\begingroup\$

I'm having some trouble checking if one faction is hostile to another faction in my RPG. I'm using enums for my factions and relations but I feel like there's a better way to approach this.
Currently this is my code for the factions (copy+paste ruined format...)

public enum Faction {
/*
 * Position 1 = Humans
 * Position 2 = Elves
 * Position 3 = Dwarves
 * Position 4 = Orcs
 * Position 5 = Undead
 * Position 6 = Trolls
 * ....
 */

Human(Relations.Friendly/*Humans*/, Relations.Neutral/*Elves*/, Relations.Neutral/*Dwarves*/, Relations.Neutral/*Orcs*/, Relations.Hostile/*Undead*/, Relations.Hostile/*Trolls*/),
Elf(Relations.Neutral, Relations.Friendly, Relations.Neutral, Relations.Neutral, Relations.Hostile, Relations.Hostile),
Dwarf(Relations.Neutral, Relations.Neutral, Relations.Friendly, Relations.Neutral, Relations.Hostile, Relations.Hostile),
Orc(Relations.Neutral, Relations.Neutral, Relations.Neutral, Relations.Friendly, Relations.Hostile, Relations.Friendly),
Undead(Relations.Hostile, Relations.Hostile, Relations.Hostile, Relations.Hostile, Relations.Friendly, Relations.Hostile),
Troll(Relations.Hostile, Relations.Hostile, Relations.Hostile,Relations.Friendly, Relations.Hostile, Relations.Friendly);

    Faction(Relations... relation) {

    }


}

enum Relations {
    Hostile(),
    Neutral(),
    Friendly();
}

class FactionHandler {
    public boolean checkIfHostile(Faction faction1, Faction faction2) {
        //How do I check?
    }
}

The lazy way to do this would be to do lots of if/else statements but I want a more clean way to check.

Basically I want it to check faction1's relation with faction2, and return true if they are enemies, and false if they are friends. Feel free to entirely scrap my code if there's a better solution to this problem as this code is mainly just me experimenting.

\$\endgroup\$
5
\$\begingroup\$

Build a 2D array of enum Relations, where row and column are the Factions.

You can then check using a simple array lookup:

public boolean checkIfHostile(Faction faction1, Faction faction2) {
    return relations[faction1.ordinal()][faction2.ordinal()] == Relations.Hostile;
}

Fill it in making sure [x][y] == [y][x] and that the diagonal [i][i] is Friendly or Neutral.

For example:

Relations[][] relations = new Relations[][]{
  { Relations.Friendly/*Humans*/, Relations.Neutral/*Elves*/, Relations.Neutral/*Dwarves*/, Relations.Neutral/*Orcs*/, Relations.Hostile/*Undead*/, Relations.Hostile/*Trolls*/},
  { Relations.Neutral,  Relations.Friendly,  ...},
  ...,
};

(I didn't fill in the whole table for readability, just keep going where the ... are)

\$\endgroup\$
  • \$\begingroup\$ Thanks! I didn't know that ordinal was a thing but it's certainly useful in my situation. I'll test this out tomorrow when I get up (it's super late here). \$\endgroup\$ – Charanor Jan 7 '16 at 5:22
  • 2
    \$\begingroup\$ Note that [x][y] == [y][x] is not mandatory. You can have faction A love faction B, but faction B could hate faction A ;-) \$\endgroup\$ – Kromster says support Monica Jan 7 '16 at 20:31
  • \$\begingroup\$ True, but it isn't clear which parameter means what in the current function prototype as initially stated in the question: checkIfHostile(Elf, Human) does it means elves are hostile to humans or human are hostile to elves? To me it implies an interchangeable bidirectional relation. \$\endgroup\$ – Stephane Hockenhull Jan 8 '16 at 6:21
  • 2
    \$\begingroup\$ You could make an extension method called IsHostalWith() for Faction: if (Elf.IsHostalWith(Human)) \$\endgroup\$ – Doug.McFarlane Jan 12 '16 at 23:44
0
\$\begingroup\$

While your solution works if you extends the Faction to hold the Relations it has the problem of each relation being specified twice. First you specify that the relation from Humans to Elfs is neutral, and then you also specify that the relation from Elfs to Humans is also neutral, this leads room for a problem where the relations are different depending on the point of view (unless that's what you're going for, it would mean some interesting game mechanics).

One way to fix this in the way you've implemented this is check both relations and return the "worst" one;

public enum Faction {
    /*
     * Position 1 = Humans
     * Position 2 = Elves
     * Position 3 = Dwarves
     * Position 4 = Orcs
     * Position 5 = Undead
     * Position 6 = Trolls
     * ....
     */

    Human(Relations.Friendly/*Humans*/, Relations.Neutral/*Elves*/, Relations.Neutral/*Dwarves*/, Relations.Neutral/*Orcs*/, Relations.Hostile/*Undead*/, Relations.Hostile/*Trolls*/),
    Elf(Relations.Neutral, Relations.Friendly, Relations.Neutral, Relations.Neutral, Relations.Hostile, Relations.Hostile),
    Dwarf(Relations.Neutral, Relations.Neutral, Relations.Friendly, Relations.Neutral, Relations.Hostile, Relations.Hostile),
    Orc(Relations.Neutral, Relations.Neutral, Relations.Neutral, Relations.Friendly, Relations.Hostile, Relations.Friendly),
    Undead(Relations.Hostile, Relations.Hostile, Relations.Hostile, Relations.Hostile, Relations.Friendly, Relations.Hostile),
    Troll(Relations.Hostile, Relations.Hostile, Relations.Hostile,Relations.Friendly, Relations.Hostile, Relations.Friendly);

    public final Relations[] relations;

    Faction(Relations... relations) {
        this.relations = relations;
    }
}

public enum Relations {
    Hostile,
    Neutral,
    Friendly
}    

public class FactionHandler {
    public Relations getRelations(Faction faction1, Faction faction2) {
        Relations relations1to2 = faction1.relations[faction2.ordinal()];
        Relations relations2to1 = faction2.relations[faction1.ordinal()];
        return relations1to2.ordinal() < relations2to1.ordinal() ? relations1to2 : relations2to1;
    }

    public boolean checkIfHostile(Faction faction1, Faction faction2) {
        return getRelations(faction1, faction2) == Relations.Hostile;
    }
}     

If you want to avoid having that problem, and if you relations can change during the game you might want to store the Relations in a Map keyed on the Faction,Faction pair. A simplified version of this (without error handling for you forgetting to set a Relation for a specific pair) could look like this;

public enum Faction { Humans, Elves, Dwarves, Orcs, Undead, Trolls }

public enum Relations {
    Hostile,
    Neutral,
    Friendly
}    

public class FactionPair {
    private final Faction factionA;
    private final Faction factionB;

    public FactionPair(Faction factionA, Faction factionB) {
        // This makes sure that if you do 
        //   new FactionPair(Humans, Orcs) 
        // or
        //   new FactionPair(Orcs, Humans)
        // they both end up the same.
        this.factionA = factionA.ordinal() < factionB.ordinal() ? factionA : factionB;
        this.factionB = factionA.ordinal() < factionB.ordinal() ? factionB : factionA;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof FactionPair) {
            FactionPair other = (FactionPair)obj;
            return other.factionA.equals(factionA) && other.factionB.equals(factionB);
        }
        else
            return false;
    }

    @Override
    public int hashCode() {
        return factionA.hashCode() ^ factionB.hashCode();
    }
}

public class FactionHandler {
    private final Map<FactionPair, Relations> map = new HashMap<FactionPair, Relations>();

    public void setRelations(Faction factionA, Faction factionB, Relations relations) {
        map.put(new FactionPair(factionA, factionB), relations);
    }

    public void isHostile(Faction factionA, Faction factionB) {
        return map.get(new FactionPair(factionA, factionB));
    }
}

You would then set your relations like this;

FactionHandler factionHandler = new FactionHandler();
factionHandler.setRelations(Faction.Humans, Factions.Orcs, Relations.Hostile);

bool areOrsHostileTowardsHumans = factionHandler.isHostile(Factions.Orcs, Factions.Humans);
\$\endgroup\$
  • \$\begingroup\$ This is an interesting way to do this, but I'm allowing the player to change his personal relationship between different races and factions freely. The way I've my game works is that you can't attack friendly factions, so if you want to "betray" a faction you have to set that faction to hostile so you can attack it. Of course I don't want this to actually change what a certain faction thinks of you, which is why I need separate relations. (Edit reason: Accidentally pressed enter...) \$\endgroup\$ – Charanor Jan 7 '16 at 12:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.