In Twilight Imperium, players trade, influence and fight each other to become Emperor of the Galaxy. Each player leads a unique faction, each with unique abilities and technologies.
The group I play with favours some factions over others, and there is usually a meta-game in deciding who gets to play as which faction.
We could randomly assign factions to each player, but we've found it more rewarding to pick factions a week or so before we play. Understanding your faction before playing the game give each player the time to plan a strategy.
What's the most fair way to assign factions? I've used an algorithm similar to one which solves the Stable Marriage Problem. I wanted to give preference to players who came in last place in the previous game. Players who had a bad game last time have the opportunity to change their faction and hopefully play a better game next time.
All players select their 6 favourite factions, in order of preference.
Start with all players' first choices:
If only one player has picked a particular faction, then that player gets that faction.
If more than one player has selected the same faction, then the player who scored the fewest points in the previous game gets their faction. The player who did not get the faction they picked first, will then move to their second preference.
Continue the picking process for player preferences 2-6 until all players have a faction.
Here's my code as it stands at the moment.
You'll need to add the following files to the same folder that you save this Python file:
Faction picker folder
factionpicker.py
player_choices.csv (a csv file containing the output of the faction picking survey)
factions.txt (a text file containing a list of all the possible factions to play as)
NOTE: The faction names in factions.txt must match exactly the names of the factions in player_choices.csv, matching case.
Copy the code into a folder, add the files listed above, run the Python script, et voila, you should have a list of which faction each player has.
import csv, sys
from operator import itemgetter
def main():
# checks correct number of args
if len(sys.argv) != 3:
sys.exit('Usage: "python project.py <name of votes file>.csv factions.txt')
players = create_players(sys.argv[1])
factions = get_factions(sys.argv[2])
table = make_table(players, factions)
print(table)
def create_players(c):
"""
The create_players() function:
- validates that the player_choices.csv argument is a CSV
- returns a list of dicts of player_choices
"""
# validate file formats of the arguments
if c.endswith(".csv") == False:
sys.exit('Usage: "python project.py <name of votes file>.csv factions.txt')
# list to save choices for each player
player_choices = []
# load player choices
with open(c) as file:
# skip the header row
next(file)
reader = csv.reader(file)
for row in reader:
timestamp, priority, name, *choices = row
player_choices.append({"Priority": priority, "Name": name, "Choices": choices})
return player_choices
def get_factions(f):
"""
The get_factions() function:
- validates that the factions.txt argument is a TXT
- returns a list of factions
"""
# validate file formats of the arguments
if f.endswith(".txt") == False:
sys.exit('Usage: "python project.py <name of votes file>.csv factions.txt')
# list saves factions
factions = []
# load factions
with open(f) as file:
lines = file.readlines()
for line in lines:
factions.append(line.strip())
return factions
def make_table(p, f):
"""
The make_table() function:
- takes the player choices dict and the factions list as input
- assigns a faction to each player
- returns a new list of dicts detailing which faction each player has
"""
table_players = []
"""
This section iterates through the player choices.
Players should get their highest choice which is still available.
First we itereate through the first choices.
If a faction is still available for that player, they get that faction and the faction is removed from factions list.
If faction is not available, then the program looks to that player's second choice.
(But only after all first choices are assigned).
"""
for n in range(6):
for player in sorted(p, key=itemgetter('Priority')):
faction = player["Choices"][n]
if faction in f:
# add player and their faction pick to final list of players
table_players.append({"Name": player["Name"], "Faction": faction})
# remove faction and player from the draw, since these have both been used
f.remove(faction)
p.remove(player)
else:
continue
# break out of loop once all players have a faction
if len(table_players) == 6:
return table_players
# TODO - Assign start position and colour to each player
# TODO - Print PDF with table position, colour and
if __name__ == "__main__":
main()
I want to make the following improvements in roughly this order:
Output a .TXT file containing the final list of players and their assigned faction
Output as a .PDF containing final list of players, their assigned faction, their meeple colour and their table position
Clean code up, making better use of classes
Build a webapp which captures data and outputs the results in one go.