Calculating the Number of Faction Combinations in Root

Why did I do this…

I played my first game of Root yesterday, a 3-player game featuring the Marquise de Cat, the Eyrie Dynasties, and the Woodland Alliance. It was a phenomenally good time, with many wild occurrences and brilliant moves that came seemingly out of nowhere, but I’ll save the gushing for my inevitable review.

It was one of those rare occasions where we sat and just talked about the game for a good half-hour after it ended. Most of it was “If only I had done…” or “Remember when…”, but the question came up, with all the factions (we have the expansion), how many possible combinations are there across player counts?

If you aren’t familiar with Root, there are 8 factions across the core game and the expansion:

  • Core game:
    • Marquise de Cat
    • Eyrie Dynasty
    • Woodland Alliance
    • Vagabond
  • Expansion:
    • Lizard Cult
    • Riverfolk Company
    • Vagabond (there can be two Vagabonds in one game)
    • Mechanical Marquise (an AI faction that can be used in place of Marquise de Cat)

At first we thought the total combinations would be a simple combination calculation, when we remembered there are 2 Vagabonds. Then there is the automaton Mechanical Marquise, which can be used in place of the actual Marquise for solo, competitive or cooperative modes.

The Vagabond Conundrum

Let’s ignore the Mechanical Marquise for now. With regards to Vagabonds, without duplicates, it would be a simple matter of calculating the possible combinations for each player count from 2 to 7 (yes, I’m aware the box says 2 to 6, but 7 is technically possible, just not recommended – see this post by Patrick Leder). But this would result in duplicate combinations when only a single Vagabond is involved.

To illustrate, I’ll give an example. Let’s refer to the 2 Vagabonds as Vagabond A and Vagabond B. A standard combination calculation would include the following 2 4-player combinations:

  1. Eyrie Dynasties, Marquise de Cat, Vagabond A, Woodland Alliance
  2. Eyrie Dynasties, Marquise de Cat, Vagabond B, Woodland Alliance

However, they are both the same combination in essence, because Vagabond A and Vagabond B are really no different from each other. So we would have to account for duplicates in any combination calculations.

The Mechanical Marquise Situation

And now for the Mechanical Marquise (or MM). It replaces the Marquise de Cat in-place, so they can’t both appear in the same game. It is also only usable in player counts of up to 6 for this same reason. What we would need to do is, for every combination including the Marquise de Cat, at every player count, decrease the player count by one.

For example, the following 6 player combination:

  • Eyrie Dynasties, Lizard Cult, Marquise de Cat, Vagabond A, Vagabond B, Woodland Alliance

Would translate into the following 5 player combination:

  • Eyrie Dynasties, Lizard Cult, Mechanical Marquise (not counted as a player), Vagabond A, Vagabond B, Woodland Alliance

The Solution

So, naturally, I decided to write a quick Python script for this, and as I was writing, I figured I might put it up here with the results… because why not.

If you are not interested in my approach and just want to see the results, scroll down to the section titled “Conclusion”. Everyone else, carry on reading at your own peril.

Programmers, be forewarned that I will be simplifying my description of some concepts for the purposes of this post. I also do not guarantee the quality of my code. In fact, I guarantee the lack of quality. I am not a Python guru, this was done quick ‘n dirty and is purely for entertainment. And I am well-aware that there are significantly more efficient ways to do this!

With that disclaimer out of the way, here is the (rather gross) script:

import itertools
from collections import OrderedDict

factions = ['Alliance', 'Cult', 'Eyrie', 'Marquise', 'Riverfolk', 'Vagabond', 'Vagabond']

print('Combinations without MM')

total_combinations = 0
for x in range(2, 8):
    set_of_combinations = set(itertools.combinations(factions, x))
    combinations_count = len(set_of_combinations)
    print('- ' + str(x) + 'p: ' + str(combinations_count))
    total_combinations += combinations_count

print('- Total: ' + str(total_combinations))

print('Combinations with MM')

total_mm_combinations = 0
for x in range(1, 7):
    set_of_combinations = set(itertools.combinations(factions, x + 1))
    mm_combinations_count = 0
    for combination in set_of_combinations:
        if 'Marquise' in combination:
            mm_combinations_count += 1
    print('- ' + str(x) + 'p: ' + str(mm_combinations_count))
    total_mm_combinations += mm_combinations_count

print('- Total: ' + str(total_mm_combinations))

print('Grand Total: ' + str(total_combinations + total_mm_combinations))

Combinations Not Including Mechanical Marquise

In the first section, the main thing the script does is calculate and print the count of combinations that do not include the MM:

print('Combinations without MM')

total_combinations = 0
for x in range(2, 8):
    set_of_combinations = set(itertools.combinations(factions, x))
    combinations_count = len(set_of_combinations)
    print('- ' + str(x) + 'p: ' + str(combinations_count))
    total_combinations += combinations_count

print('- Total: ' + str(total_combinations))

Let’s look at the loop first:

for x in range(2, 8):

This iterates from 2 up to 8, without actually including 8. So x starts as 2, goes through the tabbed code, then x becomes 3, goes through the tabbed code, etc, until x becomes 7, goes through the tabbed code, and the loop exits. This is to represent possible player counts in a non-MM game.

I then use itertools.combinations() to create a set of possible combinations:

set_of_combinations = set(itertools.combinations(factions, x))

This is where the Vagabond duplicate removal magic happens, as sets cannot contain duplicate values. What this means is, going back to my original example:

  1. Eyrie Dynasties, Marquise de Cat, Vagabond A, Woodland Alliance
  2. Eyrie Dynasties, Marquise de Cat, Vagabond B, Woodland Alliance

I’m not differentiating Vagabond A and Vagabond B in my code, so it would look like this to the set:

  1. Eyrie Dynasties, Marquise de Cat, Vagabond, Woodland Alliance
  2. Eyrie Dynasties, Marquise de Cat, Vagabond, Woodland Alliance

So one of the duplicates disappears from the set.

I then count the number of combinations for the currently looping player count:

combinations_count = len(set_of_combinations)

And I print it out:

print('- ' + str(x) + 'p: ' + str(combinations_count))

This results in an output like the following:

- 4p: 25

I add the combinations count to the combinations total across player counts:

total_combinations += combinations_count

This all happens for every player count from 2 to 7. Once the loop is complete, I finally print out the combinations total across player counts:

print('- Total: ' + str(total_combinations))

And then we move on to the next section.

Combinations Including Mechanical Marquise

Here, I calculate and print the count of combinations that include the MM:

print('Combinations with MM')

total_mm_combinations = 0
for x in range(1, 7):
    set_of_combinations = set(itertools.combinations(factions, x + 1))
    mm_combinations_count = 0
    for combination in set_of_combinations:
        if 'Marquise' in combination:
            mm_combinations_count += 1
    print('- ' + str(x) + 'p: ' + str(mm_combinations_count))
    total_mm_combinations += mm_combinations_count

print('- Total: ' + str(total_mm_combinations))

Let’s look at the loop:

for x in range(1, 7):

Quite similar to the last loop, this iterates from 1 to 7, but doesn’t include 7. This is to represent 1 to 6 as the possible player counts in a MM-included game.

As before, I create a set of combinations, which removes duplicate combinations, but instead of deriving my count directly from that set, I instead perform another loop:

for combination in set_of_combinations:
    if 'Marquise' in combination:
        mm_combinations_count += 1

What I’m doing here is only counting a combination when it includes the Marquise. The reason for this is the MM is the in-place replacement for the Marquise de Cat, so only combinations that include the Marquise should be counted towards the MM total.

I then print out the number of combinations for the currently looping player count:

print('- ' + str(x) + 'p: ' + str(mm_combinations_count))

This results in an output like the following:

- 4p: 11

I add the combinations count to the combinations total across player counts:

total_mm_combinations += mm_combinations_count

This all happens for every player count from 1 to 6. Once the loop is complete, I finally print out the combinations total across player count:

print('- Total: ' + str(total_mm_combinations))

Calculating the Grand Total

This part is easy. I simply print out the total combinations plus the total MM combinations.

print('Grand Total: ' + str(total_combinations + total_mm_combinations))

Conclusion

And now – drumroll please – the numbers!

Combinations without MM
- 2p: 16
- 3p: 25
- 4p: 25
- 5p: 16
- 6p: 6
- 7p: 1
- Total: 89
Combinations with MM
- 1p: 5
- 2p: 11
- 3p: 14
- 4p: 11
- 5p: 5
- 6p: 1
- Total: 47
Grand Total: 136

In summary, there are a total of 136 possible faction combinations, with 131 possible competitive (136 minus 5 solo), 42 possible cooperative (47 minus 5 solo), and 5 possible solo. So an absolute total of 178 ways to play! That’s a decent amount of variety!

So anyway, there we are. And what have we learned out of this? Well, probably not much really, but it was fun. And as a note to Leder Games, I think Root needs a Python faction in its next expansion.