Skip to content

Commit

Permalink
Fix swiss scheduling (#328)
Browse files Browse the repository at this point in the history
  • Loading branch information
evroon authored Nov 11, 2023
1 parent 68aff95 commit 664b13b
Show file tree
Hide file tree
Showing 14 changed files with 78 additions and 735 deletions.
4 changes: 4 additions & 0 deletions backend/bracket/logic/scheduling/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ async def create_rounds_for_new_stage_item(tournament_id: int, stage_item: Stage
rounds_count = get_number_of_rounds_to_create_round_robin(stage_item.team_count)
case StageType.SINGLE_ELIMINATION:
rounds_count = get_number_of_rounds_to_create_single_elimination(stage_item.team_count)
case StageType.SWISS:
return None
case other:
raise NotImplementedError(f'No round creation implementation for {other}')

Expand Down Expand Up @@ -60,6 +62,8 @@ async def build_matches_for_stage_item(stage_item: StageItem, tournament_id: int
await build_round_robin_stage_item(tournament_id, stage_item_with_rounds)
case StageType.SINGLE_ELIMINATION:
await build_single_elimination_stage_item(tournament_id, stage_item_with_rounds)
case StageType.SWISS:
return None

case _:
raise HTTPException(
Expand Down
127 changes: 0 additions & 127 deletions backend/bracket/logic/scheduling/ladder_players_iter.py

This file was deleted.

11 changes: 5 additions & 6 deletions backend/bracket/logic/scheduling/ladder_teams.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@
MatchFilter,
MatchWithDetailsDefinitive,
SuggestedMatch,
SuggestedVirtualMatch,
)
from bracket.sql.rounds import get_rounds_for_stage_item
from bracket.sql.teams import get_teams_with_members


async def todo_get_possible_upcoming_matches_for_teams(
tournament_id: int, filter_: MatchFilter, stage_id: int
) -> list[SuggestedMatch | SuggestedVirtualMatch]:
suggestions: list[SuggestedMatch | SuggestedVirtualMatch] = []
rounds = await get_rounds_for_stage_item(tournament_id, stage_id) # TODO: fix stage item id
async def get_possible_upcoming_matches_for_teams(
tournament_id: int, filter_: MatchFilter, stage_item_id: int
) -> list[SuggestedMatch]:
suggestions: list[SuggestedMatch] = []
rounds = await get_rounds_for_stage_item(tournament_id, stage_item_id)
draft_round = next((round_ for round_ in rounds if round_.is_draft), None)
if draft_round is None:
raise HTTPException(400, 'There is no draft round, so no matches can be scheduled.')
Expand Down
2 changes: 1 addition & 1 deletion backend/bracket/logic/scheduling/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def check_team_combination_adheres_to_filter(

suggested_match = get_suggested_match(team1, team2)

if suggested_match.elo_diff < filter_.elo_diff_threshold:
if suggested_match.elo_diff <= filter_.elo_diff_threshold:
return suggested_match

return None
12 changes: 5 additions & 7 deletions backend/bracket/logic/scheduling/upcoming_matches.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from fastapi import HTTPException

from bracket.logic.scheduling.ladder_players_iter import get_possible_upcoming_matches_for_players
from bracket.models.db.match import MatchFilter, SuggestedMatch, SuggestedVirtualMatch
from bracket.logic.scheduling.ladder_teams import get_possible_upcoming_matches_for_teams
from bracket.models.db.match import MatchFilter, SuggestedMatch
from bracket.models.db.round import Round
from bracket.models.db.stage_item import StageType
from bracket.sql.stages import get_full_tournament_details
Expand All @@ -10,16 +10,14 @@

async def get_upcoming_matches_for_swiss_round(
match_filter: MatchFilter, round_: Round, tournament_id: int
) -> list[SuggestedMatch | SuggestedVirtualMatch]:
) -> list[SuggestedMatch]:
[stage] = await get_full_tournament_details(tournament_id, stage_item_id=round_.stage_item_id)
assert len(stage.stage_items) == 1
[stage_item] = stage.stage_items

if stage_item.type is not StageType.SWISS:
raise HTTPException(400, 'There is no draft round, so no matches can be scheduled.')

upcoming_matches = await get_possible_upcoming_matches_for_players(
tournament_id, match_filter, assert_some(stage_item.id), assert_some(round_.id)
return await get_possible_upcoming_matches_for_teams(
tournament_id, match_filter, assert_some(stage_item.id)
)

return upcoming_matches
7 changes: 0 additions & 7 deletions backend/bracket/models/db/match.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,6 @@ class MatchFilter(BaseModel):
iterations: int


class SuggestedVirtualMatch(BaseModel):
team1_winner_from_stage_item_id: int
team1_position_in_group: int
team2_winner_from_stage_item_id: int
team2_position_in_group: int


class SuggestedMatch(BaseModel):
team1: TeamWithPlayers
team2: TeamWithPlayers
Expand Down
11 changes: 0 additions & 11 deletions backend/bracket/models/db/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,6 @@ class TeamWithPlayers(BaseModel):
draws: int
losses: int

@classmethod
def from_players(cls, players: list[Player]) -> TeamWithPlayers:
return TeamWithPlayers(
players=players,
elo_score=Decimal(sum(p.elo_score for p in players) / len(players)),
swiss_score=Decimal(sum(p.swiss_score for p in players) / len(players)),
wins=sum(p.wins for p in players) // len(players),
draws=sum(p.draws for p in players) // len(players),
losses=sum(p.losses for p in players) // len(players),
)

@property
def player_ids(self) -> list[int]:
return [assert_some(player.id) for player in self.players]
Expand Down
11 changes: 7 additions & 4 deletions backend/bracket/routes/matches.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
)
from bracket.models.db.round import Round
from bracket.models.db.user import UserPublic
from bracket.models.db.util import RoundWithMatches
from bracket.routes.auth import user_authenticated_for_tournament
from bracket.routes.models import SingleMatchResponse, SuccessResponse, UpcomingMatchesResponse
from bracket.routes.util import match_dependency, round_dependency
from bracket.routes.util import match_dependency, round_dependency, round_with_matches_dependency
from bracket.sql.courts import get_all_courts_in_tournament
from bracket.sql.matches import sql_delete_match, sql_update_match
from bracket.utils.types import assert_some

Expand All @@ -34,7 +36,7 @@
)
async def get_matches_to_schedule(
tournament_id: int,
elo_diff_threshold: int = 100,
elo_diff_threshold: int = 200,
iterations: int = 200,
only_behind_schedule: bool = False,
limit: int = 50,
Expand Down Expand Up @@ -109,7 +111,7 @@ async def create_matches_automatically(
iterations: int = 200,
only_behind_schedule: bool = False,
_: UserPublic = Depends(user_authenticated_for_tournament),
round_: Round = Depends(round_dependency),
round_: RoundWithMatches = Depends(round_with_matches_dependency),
) -> SuccessResponse:
if not round_.is_draft:
raise HTTPException(400, 'There is no draft round, so no matches can be scheduled.')
Expand All @@ -120,8 +122,9 @@ async def create_matches_automatically(
limit=1,
iterations=iterations,
)
courts = await get_all_courts_in_tournament(tournament_id)

limit = 15
limit = len(courts) - len(round_.matches)
for __ in range(limit):
all_matches_to_schedule = await get_upcoming_matches_for_swiss_round(
match_filter, round_, tournament_id
Expand Down
4 changes: 2 additions & 2 deletions backend/bracket/routes/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from bracket.models.db.club import Club
from bracket.models.db.court import Court
from bracket.models.db.match import Match, SuggestedMatch, SuggestedVirtualMatch
from bracket.models.db.match import Match, SuggestedMatch
from bracket.models.db.player import Player
from bracket.models.db.stage_item_inputs import (
StageItemInputOptionFinal,
Expand Down Expand Up @@ -56,7 +56,7 @@ class StagesWithStageItemsResponse(DataResponse[list[StageWithStageItems]]):
pass


class UpcomingMatchesResponse(DataResponse[list[SuggestedMatch | SuggestedVirtualMatch]]):
class UpcomingMatchesResponse(DataResponse[list[SuggestedMatch]]):
pass


Expand Down
21 changes: 8 additions & 13 deletions backend/bracket/routes/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from bracket.models.db.team import FullTeamWithPlayers, Team
from bracket.models.db.util import RoundWithMatches, StageItemWithRounds, StageWithStageItems
from bracket.schema import matches, rounds, teams
from bracket.sql.rounds import get_round_by_id
from bracket.sql.stage_items import get_stage_item
from bracket.sql.stages import get_full_tournament_details
from bracket.sql.teams import get_teams_with_members
Expand All @@ -30,20 +31,14 @@ async def round_dependency(tournament_id: int, round_id: int) -> Round:


async def round_with_matches_dependency(tournament_id: int, round_id: int) -> RoundWithMatches:
stages = await get_full_tournament_details(
tournament_id, no_draft_rounds=False, round_id=round_id
)

for stage in stages:
for stage_item in stage.stage_items:
for round_ in stage_item.rounds:
if round_ is not None:
return round_
round_ = await get_round_by_id(tournament_id, round_id)
if round_ is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Could not find round with id {round_id}",
)

raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Could not find round with id {round_id}",
)
return round_


async def stage_dependency(tournament_id: int, stage_id: int) -> StageWithStageItems:
Expand Down
11 changes: 0 additions & 11 deletions backend/bracket/sql/players.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,6 @@ async def get_all_players_in_tournament(tournament_id: int) -> list[Player]:
return [Player.parse_obj(x._mapping) for x in result]


async def get_active_players_in_tournament(tournament_id: int) -> list[Player]:
query = '''
SELECT *
FROM players
WHERE players.tournament_id = :tournament_id
AND players.active IS TRUE
'''
result = await database.fetch_all(query=query, values={'tournament_id': tournament_id})
return [Player.parse_obj(x._mapping) for x in result]


async def update_player_stats(
tournament_id: int, player_id: int, player_statistics: PlayerStatistics
) -> None:
Expand Down
15 changes: 15 additions & 0 deletions backend/bracket/sql/rounds.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from bracket.database import database
from bracket.models.db.util import RoundWithMatches
from bracket.sql.stage_items import get_stage_item
from bracket.sql.stages import get_full_tournament_details


async def get_rounds_for_stage_item(
Expand All @@ -16,6 +17,20 @@ async def get_rounds_for_stage_item(
return stage_item.rounds


async def get_round_by_id(tournament_id: int, round_id: int) -> RoundWithMatches | None:
stages = await get_full_tournament_details(
tournament_id, no_draft_rounds=False, round_id=round_id
)

for stage in stages:
for stage_item in stage.stage_items:
for round_ in stage_item.rounds:
if round_ is not None:
return round_

return None


async def get_next_round_name(tournament_id: int, stage_item_id: int) -> str:
query = '''
SELECT count(*) FROM rounds
Expand Down
Loading

1 comment on commit 664b13b

@vercel
Copy link

@vercel vercel bot commented on 664b13b Nov 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.