# Round-Robin Python program

`[Back to page on Round-Robin schedules]`
The Wikipedia article on Round-robin tournament describes the standard Circle algorithm for generating Round Robin schedules. I wrote this Python program to implement the Circle algorithm.

`################[BEGIN SOURCE CODE]################`
```# -*- coding: utf-8 -*-
"""
round_robin.py
written by Stephen R. Ferg
placed in the public domain 2021-10-01

A Python program to generate round-robin schedules
for up to NUMBER_OF_TEAMS teams.
It uses the Circle algorithm described in
https://en.wikipedia.org/wiki/Round-robin_tournament

Output is written to the console.
"""
import random

##################################################
#  CUSTOMIZABLE SETTINGS
##################################################
# Customize the value for NUMBER_OF_TEAMS.
# Specify an even number, not an odd number.
NUMBER_OF_TEAMS = 16
##################################################
#  CUSTOMIZABLE SETTINGS: end
##################################################

COMPETITIONS = []
TERRAINS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
HEADINGS = ["WIN?", "OUR SCORE", "OPPONENTS", "DIFF"]

UNDERSCORES = "_" * 4
COLUMN_SEPARATOR = " " * 2
RESULTS_LINE = ""
COLUMN_WIDTH = 9

NEW_PAGE_SYMBOL = "[]"
BANNER_CHARACTER = "#"
BANNER_LINE_LEN = 65
BANNER_LINE = BANNER_CHARACTER * BANNER_LINE_LEN

trash, there_is_an_odd_number_of_teams = divmod(NUMBER_OF_TEAMS, 2)
if there_is_an_odd_number_of_teams:
raise Exception("Number of teams must be an even number. Number of teams: "
+ str(NUMBER_OF_TEAMS))

def paren(*args):
s = strr(*args)
return strr("(", s, ")")

def competition_description(numberOfTeams):
return strr("Round Robin :: ", numberOfTeams, " teams")

number_of_rounds = len (list_of_rounds)
return strr("Round ", round_id, " of ", number_of_rounds)

s = strr(round_id).rjust(2)
return strr("Round ", s, ":")

def terrain_bracket(*args):
s = strr(*args)
return strr("{", s, "}")

s = strr(*args)
return s.ljust(23)

def print_banner(*args
, line2=None
):
say(NEW_PAGE_SYMBOL)
say(BANNER_LINE)
line1 = strr(*args)
for line in [line1, line2]:
if not line: continue
s = line.center(BANNER_LINE_LEN - 2)
s = strr(BANNER_CHARACTER, s, BANNER_CHARACTER)
say(s)
say(BANNER_LINE)

def strr(*args):
x = [str(arg) for arg in args]
y = "".join(x)
return y

def say(*args):
print(strr(*args))

def generate_column_lines():
s = ""
s = s + contents + COLUMN_SEPARATOR

s = ""
contents = "_" * COLUMN_WIDTH
s = s + contents + COLUMN_SEPARATOR
RESULTS_LINE = s

return strr("TEAM ", team.team_id, ".")

############################################################
# class Competition
############################################################
class Competition:
def __init__(self, numberOfTeams):
self.numberOfTeams = numberOfTeams
self.numberOfRounds = self.numberOfTeams - 1
self.gamesPerRound = self.numberOfTeams // 2
self.numberOfTerrains, remainder = divmod(numberOfTeams, 2)
self.game_terrains = list(TERRAINS[0:self.numberOfTerrains])

self.teams = []
self.rounds = []

self.generate_teams_in_competition()
self.generate_rounds_in_competition()

def generate_teams_in_competition(self):
self.teams = []
for i in range(self.numberOfTeams):
team_id = i + 1
team = Team(team_id, self)
self.teams.append(team)

def generate_rounds_in_competition(self):
self.rounds = []
for i in range(self.numberOfRounds):
round_id = i + 1
round = Round(round_id, self)
self.rounds.append(round)
self.turn()

def turn(self):
first = self.teams[0]
last = self.teams[-1]
middle = self.teams[1:-1]

newList = [first, last]
newList.extend(middle)
self.teams = newList

print_banner("* FORMS FOR A COMPETITION OF "
, self.numberOfTeams, " TEAMS")

competition_info = competition_description(self.numberOfTeams)
print_banner("ROUNDS AND TEAM MATCH-UPS"
, line2 = competition_info
)

def print_team_results_forms(self):
for team in self.teams:
print_banner("TEAM RESULTS FORMS FOR " + format_team_header(team)
)
for round in team.get_opponents_for_rounds():
say(round)

def print_rounds_in_competition(self):
### say()
for round in self.rounds:
say()
for game in round.games:
say(game.toString())

def print_control_table_form(self):
for team in self.teams:
print_banner(strr("CONTROL TABLE RESULTS FOR ", format_team_header(team))
)
for round in team.get_opponents_for_rounds():
say(round)

############################################################
# class Round
############################################################
class Round:
def __init__(self, round_id, competition):
self.round_id = round_id
self.competition = competition

self.unassigned_terrain_ids = [x for x in competition.game_terrains]
self.games = []

teams = self.competition.teams
for i in range(self.competition.gamesPerRound):
# make one game
game_id = i + 1
# get terrain id
terrain_id = random.choice(self.unassigned_terrain_ids)
self.unassigned_terrain_ids.remove(terrain_id)

round = self
game = Game(self.competition, round, game_id, terrain_id)

offsetFromStartOfTeamsList = i
offsetFromEndOfTeamsList = len(teams) - 1 - offsetFromStartOfTeamsList
team1 = teams[offsetFromStartOfTeamsList]
team2 = teams[offsetFromEndOfTeamsList]
game.assign_teams_to_game(team1, team2)

self.games.append(game)

self.games = sorted(self.games)  # keep the list sorted

def get_printable_games_in_round(self):
x = []
for game in self.games:
s = game.toString()
x.append(s)
return x

def print_games_in_round(self):
say()
for printable_game in self.get_printable_games_in_round():
say(printable_game)

############################################################
# class Game
############################################################
class Game:
def __init__(self, competition, round, game_id, terrain_id):
self.game_id = game_id
self.terrain_id = terrain_id
self.competition = competition
self.round = round

self.team1 = None
self.team2 = None

def assign_teams_to_game(self, team1, team2):
# aesthetics: put lowest team id number on left
if team1.team_id < team2.team_id:
self.team1, self.team2 = team1, team2
else:
self.team1, self.team2 = team2, team1

def __lt__(self, other):
if self.team1.team_id < other.team1.team_id:
return True
else:
return False

def toString(self):
VERSUS = " : "
team1 = str(self.team1.team_id)
team2 = str(self.team2.team_id)
s = strr(team1.rjust(3)
, VERSUS, team2.ljust(3)
, " ", terrain_bracket("terrain " + self.terrain_id))
return s

############################################################
# class Team
############################################################
class Team:
def __init__(self, id, competition):
self.team_id = id
self.competition = competition
self.rounds = []
self.opponents_dict = {}
self.game_terrains = {}

# we assume that rounds will be added sequentially
round_id = game.round.round_id

if game.team1.team_id == self.team_id:
opponent = game.team2
else:
opponent = game.team1

if round_id in self.opponents_dict:
pass
else:
self.opponents_dict[round_id] = opponent
self.game_terrains[round_id] = game.terrain_id
self.rounds.append(round_id)

def get_opponents_for_rounds(self):
x = []
for round_id in self.rounds:
terrain = self.game_terrains[round_id]
opponent = self.opponents_dict[round_id]
, " team ", str(opponent.team_id).rjust(2)
, " ", terrain_bracket(terrain)
, "  "
, RESULTS_LINE
)

x.append(s)
return x

def __lt__(self, other):
if self.team_id < other.team_id:
return True
else:
return False

############################################################
# subroutines
############################################################

def generate_1_competition(numberOfTeams):
competition = Competition(numberOfTeams)
competition.generate_rounds_in_competition()
COMPETITIONS.append(competition)

def generate_competitions():
for numberOfTeams in range(1, NUMBER_OF_TEAMS + 1):
if numberOfTeams  0:
# an odd number of teams
continue
else:
generate_1_competition(numberOfTeams)

if __name__ == "__main__":
generate_competitions()
generate_column_lines()
say("Empty square brackets [] indicate page breaks.")
say("An asterisk * indicates start of forms for a competition.")

for competition in COMPETITIONS:
competition.print_rounds_in_competition()

print_banner("End of competitions list.")
for competition in COMPETITIONS:
`################[END SOURCE CODE]################`