3
\$\begingroup\$

I'm working on a game that follows the life of a powerful magician as she lives, dies, and reincarnates dozens of times across hundreds of years. Meanwhile, empires rise and fall around her--she can influence them by killing their kings or even becoming their leader, but even without her they will go about their business, fighting wars with other factions and appointing new leaders.

The problem is this: how do I store all those faction relationships? Currently, I have a class Faction, which has an enemy list, a ally list, and a controlled list (the Government of Rezla controls the Army of Rezla, etc). However, simply using lists like these limit me to a binary ally/not-ally, enemy/not-enemy deal.

I suppose I could get around that to some degree by using a list of lists, so RezlaGovt.allies contains [[AzosGovt, 2],[JupaGovt, 4]], where the first value in the sublist stores the name of the ally and the second value stores the current reputation, but that seems inelegant (and I don't really understand how to manage it properly in code). It also makes it much more difficult to later add tags to the relationships, like hasTradeAgreement or hasMutualDefencePact.

What data structure do other complex strategy games use to handle faction relationships?

Other Details:

I will have around 10 main factions for the world governments, each with subfactions and subsubfactions (and maybe even subsubsubfactions!), so a system that deals well with large datasets is a plus.

Not all factions need to be simulated all the time: the Fighter's Guild in a distant city might be completely ignored until the player does something in the area, at which point I can randomly generate some "history" for it, to make it seem like it's been doing stuff.

\$\endgroup\$
  • \$\begingroup\$ Have you considered a graph data structure (nodes and edges)? You could add tags to the relationships by adding labels to edges. \$\endgroup\$ – amitp Jan 16 '16 at 2:56
  • 1
    \$\begingroup\$ Related: gamedev.stackexchange.com/questions/114422 \$\endgroup\$ – Kromster says support Monica Jan 16 '16 at 8:14
  • \$\begingroup\$ Maybe someone could adapt that related answer above to a python specific answer - or create an entirely new one... \$\endgroup\$ – Lucas Siqueira Feb 4 '16 at 14:19
3
\$\begingroup\$

You could keep the factions in a dictionary inside your scenario/map/level instance, using ("groupA", "groupB") tuples as keys to store the relations between groups.

In case relations are always symmetrical/bilateral (I'm an ally for my allies, an enemy for my enemies), you could subclass dict and change it in a way that sorted tuples would be used to get/set items (thus avoiding the need to keep a pair of similar data for the reversed key). With that, you would have:

>>> factions("AzosGovt", "JupaGovt") == factions("JupaGovt", "AzosGovt")
True

That could be obtained with a subclass like this one:

class Factions(dict):

    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    def __getitem__(self, key):
        val = dict.__getitem__(self, tuple(sorted(key)))
        return val

    def __setitem__(self, key, val):
        dict.__setitem__(self, tuple(sorted(key)), val)

    def __repr__(self):
        dictrepr = dict.__repr__(self)
        return '{}({})'.format(type(self).__name__, dictrepr)

    def update(self, *args, **kwargs):
        for k, v in dict(*args, **kwargs).items():
            self[tuple(sorted(k))] = v

The value of the factions could be whatever you feel more comfortable working with.

Personally, I would go for readability and use another dictionary. If so, to set your factions up you could do:

factions = Factions()

factions.update({
    ("AzosGovt", "JupaGovt"): {
        "relation": 80,
        "trade": 1,
        "alliance": 1,
        "war": 0
    },
    ("AzosGovt", "ElvesGovt"): {
        "relation": -50,
        "trade": 0,
        "alliance": 0,
        "war": 1
    },
})

That would allow you to easily implement utility methods like those:

    def allies(self, key):
        allies = []
        for k, v in self.items():
            if v["alliance"]:
                for side in k:
                    if side != key:
                        allies.append(side)
        return allies

    def at_war(self, key):
        enemies = []
        for k, v in self.items():
            if v["war"]:
                for side in k:
                    if side != key:
                        enemies.append(side)
        return enemies

    def make_peace(self, key):
        self[tuple(sorted(key))]["war"] = 0

    def declare_war(self, key):
        self[tuple(sorted(key))]["war"] = 1
        self[tuple(sorted(key))]["alliance"] = 0

Testing it:

>>> print(factions.allies("AzosGovt"))
['JupaGovt']

>>> print(factions.allies("JupaGovt"))
['AzosGovt']

>>> print(factions.at_war("AzosGovt"))
['ElvesGovt']

>>> print(factions.at_war("ElvesGovt"))
['AzosGovt']

>>> factions.make_peace(("AzosGovt", "ElvesGovt"))
>>> factions.declare_war(("AzosGovt", "JupaGovt"))

>>> print(factions.allies("AzosGovt"))
[]

>>> print(factions.at_war("AzosGovt"))
['JupaGovt']

It's a simple implementation, but, usually, simple is good.

Main idea here is the sorted(tuple) as key.

For the relation itself you could use, instead of the dictionary, a list of booleans like the one you talked about, or create another class to hold attributes and properties like: at war, alliance, etc.

\$\endgroup\$
2
\$\begingroup\$

Have you considered a 2D array?

Visual representation:

Faction ->     |  A  |  B  |  C  |
   |           |     |     |     |
   V           |     |     |     |
----------------------------------
   A thinks... | --- | 15% | 89% |
----------------------------------
   B thinks... | 12% | --- | 40% |
----------------------------------
   C thinks... | 92% | 50% | --- |
----------------------------------

read as:
A has a 15% opinion of B
B has a 12% opinion of A
etc.

The kind of data you store might be much more complex than this, such as how The Last Federation handles it (full 'grid' view):

TLF Race Relations

This being the breakdown of "A has a 15% opinion of B" into every factor affecting that total (eg how "15%" is calculated), in this case, a total of -1392.56, which is so bad as to be irreparable.

The internal representation is essentially a dictionary of to , so that when an event occurs the is increased (or decreased!) by some value, then everything is summed (some factors being positive, others negative) and a resulting total is generated.

\$\endgroup\$

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.