Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions config/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ services:
- "othello_pgdata:/var/lib/postgresql/data"
othello_redis:
image: redis:latest
ports:
- "6379:6379"
expose:
- "6379"
volumes:
othello_pgdata:
2 changes: 2 additions & 0 deletions othello/apps/tournaments/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class TournamentCreateForm(forms.ModelForm):
game_time_limit = forms.IntegerField(label="Game Time Limit: ", min_value=1, max_value=15)
num_rounds = forms.IntegerField(label="Amount of Rounds: ", min_value=5, max_value=settings.MAX_ROUND_NUM)
include_users = forms.ModelMultipleChoiceField(label="Include Users: ", queryset=Submission.objects.latest())
pairing_algorithm = forms.ChoiceField(label="Pairing Algorithm: ", choices=Tournament.PAIRING_ALGORITHMS, initial="swiss")
round_robin_matches = forms.IntegerField(initial=2, label="Round Robin Matches Between Two Players: ")
bye_player = forms.ModelChoiceField(label="Bye Player: ", queryset=Submission.objects.latest())
runoff = forms.BooleanField(label="Enable Time Hoarding?", initial=False, required=False)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.24 on 2025-06-11 14:55

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('tournaments', '0026_remove_tournamentplayer_black_games_and_more'),
]

operations = [
migrations.AddField(
model_name='tournament',
name='pairing_algorithm',
field=models.CharField(choices=[('random', 'Random'), ('round_robin', 'Round Robin'), ('swiss', 'Swiss'), ('danish', 'Danish')], default='swiss', max_length=20),
),
]
23 changes: 23 additions & 0 deletions othello/apps/tournaments/migrations/0028_auto_20250805_2306.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.24 on 2025-08-06 03:06

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('tournaments', '0027_tournament_pairing_algorithm'),
]

operations = [
migrations.AddField(
model_name='tournament',
name='round_robin_matches',
field=models.IntegerField(default=2, help_text='How many matches to play between two players in a round robin tournament'),
),
migrations.AlterField(
model_name='tournament',
name='pairing_algorithm',
field=models.CharField(choices=[('random', 'Random'), ('swiss', 'Swiss'), ('danish', 'Danish'), ('round_robin', 'Round Robin')], default='swiss', max_length=20),
),
]
12 changes: 12 additions & 0 deletions othello/apps/tournaments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ def filter_future(self) -> "models.query.QuerySet[Tournament]":


class Tournament(models.Model):
PAIRING_ALGORITHMS = (
("random", "Random"),
("swiss", "Swiss"),
("danish", "Danish"),
("round_robin", "Round Robin"),
)

objects: Any = TournamentSet().as_manager()

Expand All @@ -39,6 +45,12 @@ class Tournament(models.Model):
related_name="bye",
default=None,
)
pairing_algorithm = models.CharField(
choices=PAIRING_ALGORITHMS,
default="swiss",
max_length=20,
)
round_robin_matches = models.IntegerField(default=2, help_text="How many matches to play between two players in a round robin tournament")

finished = models.BooleanField(default=False)
terminated = models.BooleanField(default=False)
Expand Down
27 changes: 17 additions & 10 deletions othello/apps/tournaments/pairings.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
import random
from typing import List, Tuple

from othello.apps.tournaments.models import TournamentPlayer
from othello.apps.tournaments.models import Tournament, TournamentPlayer
from othello.apps.tournaments.utils import chunks, get_updated_ranking, logger

Players = List[TournamentPlayer]
Pairings = List[Tuple[TournamentPlayer, ...]]


def random_pairing(players: Players, bye_player: TournamentPlayer) -> Pairings:
def round_robin_pairing(players: Players, _bye_player: TournamentPlayer, **kwargs) -> Pairings:
matches: Pairings = []
for i in range(len(players)):
for j in range(i + 1, len(players)):
matches.extend((players[i], players[j]) for _ in range(kwargs["round_robin_matches"]))
return matches


def random_pairing(players: Players, bye_player: TournamentPlayer, **kwargs) -> Pairings:
randomized: Players = random.sample(players, len(players))
if len(randomized) % 2 == 1:
randomized.append(bye_player)

return list(chunks(randomized, 2))


def swiss_pairing(players: Players, bye_player: TournamentPlayer) -> Pairings:
def swiss_pairing(players: Players, bye_player: TournamentPlayer, **kwargs) -> Pairings:
tournament = players[0].tournament

# Default to danish pairing if there are enough rounds
Expand Down Expand Up @@ -55,7 +63,7 @@ def swiss_pairing(players: Players, bye_player: TournamentPlayer) -> Pairings:
return matches


def danish_pairing(players: Players, bye_player: TournamentPlayer) -> Pairings:
def danish_pairing(players: Players, bye_player: TournamentPlayer, **kwargs) -> Pairings:
logger.warning("Using Danish Pairing")
matches = []
players = sorted(players, key=get_updated_ranking, reverse=True)
Expand All @@ -65,8 +73,8 @@ def danish_pairing(players: Players, bye_player: TournamentPlayer) -> Pairings:
players.append(bye_player)
# black = random.choice((players[i], players[i + 1]))
# white = players[i] if black == players[i + 1] else players[i + 1]
matches.append((players[i], players[i+1]))
matches.append((players[i+1], players[i]))
matches.append((players[i], players[i + 1]))
matches.append((players[i + 1], players[i]))

return matches

Expand All @@ -75,10 +83,9 @@ def danish_pairing(players: Players, bye_player: TournamentPlayer) -> Pairings:
"random": random_pairing,
"swiss": swiss_pairing,
"danish": danish_pairing,
"round_robin": round_robin_pairing,
}


def pair(players: Players, bye_player: TournamentPlayer, algorithm: str = "swiss") -> Pairings:
if algorithm not in algorithms:
raise ValueError(f"Invalid pairing algorithm: {algorithm}")
return algorithms[algorithm](players, bye_player)
def pair(players: Players, bye_player: TournamentPlayer, tournament: Tournament, **kwargs) -> Pairings:
return algorithms[tournament.pairing_algorithm](players, bye_player, **kwargs)
11 changes: 10 additions & 1 deletion othello/apps/tournaments/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,16 @@ def run_tournament(tournament_id: int) -> None:
bye_player = TournamentPlayer.objects.create(tournament=t, submission=t.bye_player)

for round_num in range(t.num_rounds):
matches: List[Tuple[TournamentPlayer, ...]] = pair(submissions, bye_player)
try:
matches: List[Tuple[TournamentPlayer, ...]] = pair(
submissions,
bye_player,
t,
round_robin_matches=t.round_robin_matches,
)
except Exception as e:
logger.error(f"Error in pairing for tournament {tournament_id}, round {round_num + 1}: {e}")
raise
t.refresh_from_db()
if t.terminated:
t.delete()
Expand Down
5 changes: 5 additions & 0 deletions othello/static/js/tournaments/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ window.onload = function () {
maxItems: 1,
sortField: [{'field': 'text', 'direction': 'desc'}]
});
$("#id_pairing_algorithm").selectize({
maxItems: 1,
sortField: [{'field': 'text', 'direction': 'desc'}]
});


$("#includeUsersFile").on('change', function () {
let reader = new FileReader();
Expand Down
10 changes: 9 additions & 1 deletion othello/templates/tournaments/create.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@
</p>
<br>
<br>
<p class="row" style="margin-left: 0">
{{ form.pairing_algorithm.label }}
{{ form.pairing_algorithm }}
</p>
<p class="row" style="margin-left: 0">
{{ form.round_robin_matches.label }}
{{ form.round_robin_matches }}
</p>
<p id="user_group" class="row" style="margin-left: 0; margin-bottom: 0">
{{ form.include_users.label }}
{{ form.include_users }}
Expand Down Expand Up @@ -82,4 +90,4 @@ <h4>Scheduled Tournaments</h4>
</div>
</div>
</div>
{% endblock %}
{% endblock %}
Loading