From 664b13b0db409d4edf1094e58f7e2caee7c1b756 Mon Sep 17 00:00:00 2001 From: Erik Vroon Date: Sat, 11 Nov 2023 10:59:52 +0100 Subject: [PATCH] Fix swiss scheduling (#328) --- backend/bracket/logic/scheduling/builder.py | 4 + .../logic/scheduling/ladder_players_iter.py | 127 ---- .../bracket/logic/scheduling/ladder_teams.py | 11 +- backend/bracket/logic/scheduling/shared.py | 2 +- .../logic/scheduling/upcoming_matches.py | 12 +- backend/bracket/models/db/match.py | 7 - backend/bracket/models/db/team.py | 11 - backend/bracket/routes/matches.py | 11 +- backend/bracket/routes/models.py | 4 +- backend/bracket/routes/util.py | 21 +- backend/bracket/sql/players.py | 11 - backend/bracket/sql/rounds.py | 15 + .../integration_tests/api/matches_test.py | 566 +----------------- frontend/src/pages/tournaments/[id].tsx | 11 +- 14 files changed, 78 insertions(+), 735 deletions(-) delete mode 100644 backend/bracket/logic/scheduling/ladder_players_iter.py diff --git a/backend/bracket/logic/scheduling/builder.py b/backend/bracket/logic/scheduling/builder.py index c91b2ab35..1dfa4f317 100644 --- a/backend/bracket/logic/scheduling/builder.py +++ b/backend/bracket/logic/scheduling/builder.py @@ -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}') @@ -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( diff --git a/backend/bracket/logic/scheduling/ladder_players_iter.py b/backend/bracket/logic/scheduling/ladder_players_iter.py deleted file mode 100644 index b780e322a..000000000 --- a/backend/bracket/logic/scheduling/ladder_players_iter.py +++ /dev/null @@ -1,127 +0,0 @@ -import random -from collections import defaultdict -from functools import lru_cache -from typing import cast - -from fastapi import HTTPException - -from bracket.logic.scheduling.shared import check_team_combination_adheres_to_filter -from bracket.models.db.match import ( - MatchFilter, - MatchWithDetailsDefinitive, - SuggestedMatch, - SuggestedVirtualMatch, -) -from bracket.models.db.player import Player -from bracket.models.db.team import TeamWithPlayers -from bracket.models.db.util import RoundWithMatches -from bracket.sql.players import get_active_players_in_tournament -from bracket.sql.stage_items import get_stage_item -from bracket.utils.types import assert_some - -# TODO: needs refactor -# pylint: disable=too-many-branches - - -def player_already_scheduled(player: Player, draft_round: RoundWithMatches) -> bool: - return any( - player.id in match.player_ids - for match in draft_round.matches - if isinstance(match, MatchWithDetailsDefinitive) - ) - - -async def get_possible_upcoming_matches_for_players( - tournament_id: int, filter_: MatchFilter, stage_item_id: int, round_id: int -) -> list[SuggestedMatch | SuggestedVirtualMatch]: - random.seed(10) - suggestions: set[SuggestedMatch] = set() - stage_item = await get_stage_item(tournament_id, stage_item_id) - - if stage_item is None: - raise ValueError( - f'Could not find stage item with id {stage_item_id} for tournament {tournament_id}' - ) - - draft_round = next((round_ for round_ in stage_item.rounds if round_.id == round_id), None) - other_rounds = [round_ for round_ in stage_item.rounds if not round_.is_draft] - max_matches_per_round = ( - max(len(other_round.matches) for other_round in other_rounds) - if len(other_rounds) > 0 - else 10 - ) - - @lru_cache - def team_already_scheduled_before(player1: Player, player2: Player) -> bool: - return any( - player1 in match.team1.players and player2 in match.team2.players - for round_ in other_rounds - for match in round_.matches - if isinstance(match, MatchWithDetailsDefinitive) - ) - - team_already_scheduled_before.cache_clear() - if draft_round is None: - raise HTTPException(400, 'There is no draft round, so no matches can be scheduled.') - - players = await get_active_players_in_tournament(tournament_id) - - players_match_count: dict[int, int] = defaultdict(int) - for round_ in other_rounds: - for match_ in round_.matches: - if isinstance(match_, MatchWithDetailsDefinitive): - for player_id in match_.player_ids: - players_match_count[player_id] += 1 - - for player in players: - if player.id not in players_match_count: - players_match_count[assert_some(player.id)] = 0 - - max_played_matches = max(players_match_count.values()) - player_ids_behind_schedule = [ - player_id - for player_id, played in players_match_count.items() - if played != max_played_matches - ] - players_behind_schedule = [ - player for player in players if player.id in player_ids_behind_schedule - ] - players_to_consider = players_behind_schedule if filter_.only_behind_schedule else players - players_to_schedule = [ - player - for player in players_to_consider - if not player_already_scheduled(player, draft_round) - ] - - if len(players_to_schedule) < 4: - return [] - - for i in range(filter_.iterations): - possible_players = random.sample(players_to_schedule, 4) - team1_players, team2_players = possible_players[:2], possible_players[2:4] - - if team_already_scheduled_before( - team1_players[0], team1_players[1] - ) or team_already_scheduled_before(team2_players[0], team2_players[1]): - continue - - team1 = TeamWithPlayers.from_players(team1_players) - team2 = TeamWithPlayers.from_players(team2_players) - - suggested_match = check_team_combination_adheres_to_filter(team1, team2, filter_) - if suggested_match: - suggested_match.player_behind_schedule_count = sum( - 1 if player.id in player_ids_behind_schedule else 0 - for player in team1_players + team2_players - ) - suggestions.add(suggested_match) - - result = sorted( - list(suggestions), - key=lambda s: s.elo_diff - (int(1e9) * s.player_behind_schedule_count), - ) - for i in range(min(max_matches_per_round, len(result))): - result[i].is_recommended = True - - team_already_scheduled_before.cache_clear() - return cast(list[SuggestedMatch | SuggestedVirtualMatch], result[: filter_.limit]) diff --git a/backend/bracket/logic/scheduling/ladder_teams.py b/backend/bracket/logic/scheduling/ladder_teams.py index 8f630ad48..373625558 100644 --- a/backend/bracket/logic/scheduling/ladder_teams.py +++ b/backend/bracket/logic/scheduling/ladder_teams.py @@ -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.') diff --git a/backend/bracket/logic/scheduling/shared.py b/backend/bracket/logic/scheduling/shared.py index 4a4b1cf63..6e8947e8e 100644 --- a/backend/bracket/logic/scheduling/shared.py +++ b/backend/bracket/logic/scheduling/shared.py @@ -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 diff --git a/backend/bracket/logic/scheduling/upcoming_matches.py b/backend/bracket/logic/scheduling/upcoming_matches.py index 94067a792..e9897846a 100644 --- a/backend/bracket/logic/scheduling/upcoming_matches.py +++ b/backend/bracket/logic/scheduling/upcoming_matches.py @@ -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 @@ -10,7 +10,7 @@ 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 @@ -18,8 +18,6 @@ async def get_upcoming_matches_for_swiss_round( 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 diff --git a/backend/bracket/models/db/match.py b/backend/bracket/models/db/match.py index 634c19486..9e7fd286c 100644 --- a/backend/bracket/models/db/match.py +++ b/backend/bracket/models/db/match.py @@ -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 diff --git a/backend/bracket/models/db/team.py b/backend/bracket/models/db/team.py index 810fe91fa..a073366fe 100644 --- a/backend/bracket/models/db/team.py +++ b/backend/bracket/models/db/team.py @@ -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] diff --git a/backend/bracket/routes/matches.py b/backend/bracket/routes/matches.py index 65e739d58..132ad5310 100644 --- a/backend/bracket/routes/matches.py +++ b/backend/bracket/routes/matches.py @@ -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 @@ -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, @@ -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.') @@ -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 diff --git a/backend/bracket/routes/models.py b/backend/bracket/routes/models.py index 0b0c38b6a..dc17b4112 100644 --- a/backend/bracket/routes/models.py +++ b/backend/bracket/routes/models.py @@ -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, @@ -56,7 +56,7 @@ class StagesWithStageItemsResponse(DataResponse[list[StageWithStageItems]]): pass -class UpcomingMatchesResponse(DataResponse[list[SuggestedMatch | SuggestedVirtualMatch]]): +class UpcomingMatchesResponse(DataResponse[list[SuggestedMatch]]): pass diff --git a/backend/bracket/routes/util.py b/backend/bracket/routes/util.py index ac6609ad9..93c40082c 100644 --- a/backend/bracket/routes/util.py +++ b/backend/bracket/routes/util.py @@ -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 @@ -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: diff --git a/backend/bracket/sql/players.py b/backend/bracket/sql/players.py index f4f9fb7d7..3ef1740aa 100644 --- a/backend/bracket/sql/players.py +++ b/backend/bracket/sql/players.py @@ -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: diff --git a/backend/bracket/sql/rounds.py b/backend/bracket/sql/rounds.py index c0dfc1910..890b8cc99 100644 --- a/backend/bracket/sql/rounds.py +++ b/backend/bracket/sql/rounds.py @@ -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( @@ -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 diff --git a/backend/tests/integration_tests/api/matches_test.py b/backend/tests/integration_tests/api/matches_test.py index 187f2abc8..64586991e 100644 --- a/backend/tests/integration_tests/api/matches_test.py +++ b/backend/tests/integration_tests/api/matches_test.py @@ -206,25 +206,25 @@ async def test_upcoming_matches_endpoint( update={'elo_score': 1100, 'tournament_id': auth_context.tournament.id} ), assert_some(team1_inserted.id), - ), + ) as player_inserted_1, inserted_player_in_team( DUMMY_PLAYER2.copy( update={'elo_score': 1300, 'tournament_id': auth_context.tournament.id} ), assert_some(team2_inserted.id), - ), + ) as player_inserted_2, inserted_player_in_team( DUMMY_PLAYER3.copy( update={'elo_score': 1200, 'tournament_id': auth_context.tournament.id} ), assert_some(team1_inserted.id), - ), + ) as player_inserted_3, inserted_player_in_team( DUMMY_PLAYER4.copy( update={'elo_score': 1400, 'tournament_id': auth_context.tournament.id} ), assert_some(team2_inserted.id), - ), + ) as player_inserted_4, ): json_response = await send_tournament_request( HTTPMethod.GET, f'rounds/{round_inserted.id}/upcoming_matches', auth_context, {} @@ -233,595 +233,77 @@ async def test_upcoming_matches_endpoint( 'data': [ { 'team1': { - 'id': None, + 'id': team2_inserted.id, 'players': [ { - 'id': 4, - 'active': True, - 'name': 'Player 4', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1400.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 1, - 'active': True, - 'name': 'Player 1', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1100.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'team2': { - 'id': None, - 'players': [ - { - 'id': 2, + 'id': player_inserted_2.id, 'active': True, 'name': 'Player 2', 'created': '2022-01-11T04:32:11+00:00', 'tournament_id': 1, - 'elo_score': 1300.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 3, - 'active': True, - 'name': 'Player 3', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1200.0, - 'swiss_score': 0.0, + 'elo_score': 1300, + 'swiss_score': 0, 'wins': 0, 'draws': 0, 'losses': 0, }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'elo_diff': 0.0, - 'swiss_diff': 0.0, - 'is_recommended': True, - 'player_behind_schedule_count': 0, - }, - { - 'team1': { - 'id': None, - 'players': [ { - 'id': 4, + 'id': player_inserted_4.id, 'active': True, 'name': 'Player 4', 'created': '2022-01-11T04:32:11+00:00', 'tournament_id': 1, - 'elo_score': 1400.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 1, - 'active': True, - 'name': 'Player 1', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1100.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'team2': { - 'id': None, - 'players': [ - { - 'id': 3, - 'active': True, - 'name': 'Player 3', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1200.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 2, - 'active': True, - 'name': 'Player 2', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1300.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'elo_diff': 0.0, - 'swiss_diff': 0.0, - 'is_recommended': True, - 'player_behind_schedule_count': 0, - }, - { - 'team1': { - 'id': None, - 'players': [ - { - 'id': 2, - 'active': True, - 'name': 'Player 2', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1300.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 3, - 'active': True, - 'name': 'Player 3', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1200.0, - 'swiss_score': 0.0, + 'elo_score': 1400, + 'swiss_score': 0, 'wins': 0, 'draws': 0, 'losses': 0, }, ], 'swiss_score': 0.0, - 'elo_score': 1250.0, + 'elo_score': 1350.0, 'wins': 0, 'draws': 0, 'losses': 0, }, 'team2': { - 'id': None, - 'players': [ - { - 'id': 1, - 'active': True, - 'name': 'Player 1', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1100.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 4, - 'active': True, - 'name': 'Player 4', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1400.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'elo_diff': 0.0, - 'swiss_diff': 0.0, - 'is_recommended': True, - 'player_behind_schedule_count': 0, - }, - { - 'team1': { - 'id': None, + 'id': team1_inserted.id, 'players': [ { - 'id': 1, + 'id': player_inserted_1.id, 'active': True, 'name': 'Player 1', 'created': '2022-01-11T04:32:11+00:00', 'tournament_id': 1, - 'elo_score': 1100.0, - 'swiss_score': 0.0, + 'elo_score': 1100, + 'swiss_score': 0, 'wins': 0, 'draws': 0, 'losses': 0, }, { - 'id': 4, - 'active': True, - 'name': 'Player 4', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1400.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'team2': { - 'id': None, - 'players': [ - { - 'id': 2, - 'active': True, - 'name': 'Player 2', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1300.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 3, + 'id': player_inserted_3.id, 'active': True, 'name': 'Player 3', 'created': '2022-01-11T04:32:11+00:00', 'tournament_id': 1, - 'elo_score': 1200.0, - 'swiss_score': 0.0, + 'elo_score': 1200, + 'swiss_score': 0, 'wins': 0, 'draws': 0, 'losses': 0, }, ], 'swiss_score': 0.0, - 'elo_score': 1250.0, + 'elo_score': 1150.0, 'wins': 0, 'draws': 0, 'losses': 0, }, - 'elo_diff': 0.0, - 'swiss_diff': 0.0, - 'is_recommended': True, + 'elo_diff': 200, + 'swiss_diff': 0, + 'is_recommended': False, 'player_behind_schedule_count': 0, - }, - { - 'team1': { - 'id': None, - 'players': [ - { - 'id': 3, - 'active': True, - 'name': 'Player 3', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1200.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 2, - 'active': True, - 'name': 'Player 2', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1300.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'team2': { - 'id': None, - 'players': [ - { - 'id': 4, - 'active': True, - 'name': 'Player 4', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1400.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 1, - 'active': True, - 'name': 'Player 1', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1100.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'elo_diff': 0.0, - 'swiss_diff': 0.0, - 'is_recommended': True, - 'player_behind_schedule_count': 0, - }, - { - 'team1': { - 'id': None, - 'players': [ - { - 'id': 3, - 'active': True, - 'name': 'Player 3', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1200.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 2, - 'active': True, - 'name': 'Player 2', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1300.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'team2': { - 'id': None, - 'players': [ - { - 'id': 1, - 'active': True, - 'name': 'Player 1', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1100.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 4, - 'active': True, - 'name': 'Player 4', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1400.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'elo_diff': 0.0, - 'swiss_diff': 0.0, - 'is_recommended': True, - 'player_behind_schedule_count': 0, - }, - { - 'team1': { - 'id': None, - 'players': [ - { - 'id': 1, - 'active': True, - 'name': 'Player 1', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1100.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 4, - 'active': True, - 'name': 'Player 4', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1400.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'team2': { - 'id': None, - 'players': [ - { - 'id': 3, - 'active': True, - 'name': 'Player 3', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1200.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 2, - 'active': True, - 'name': 'Player 2', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1300.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'elo_diff': 0.0, - 'swiss_diff': 0.0, - 'is_recommended': True, - 'player_behind_schedule_count': 0, - }, - { - 'team1': { - 'id': None, - 'players': [ - { - 'id': 2, - 'active': True, - 'name': 'Player 2', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1300.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 3, - 'active': True, - 'name': 'Player 3', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1200.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'team2': { - 'id': None, - 'players': [ - { - 'id': 4, - 'active': True, - 'name': 'Player 4', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1400.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - { - 'id': 1, - 'active': True, - 'name': 'Player 1', - 'created': '2022-01-11T04:32:11+00:00', - 'tournament_id': 1, - 'elo_score': 1100.0, - 'swiss_score': 0.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - ], - 'swiss_score': 0.0, - 'elo_score': 1250.0, - 'wins': 0, - 'draws': 0, - 'losses': 0, - }, - 'elo_diff': 0.0, - 'swiss_diff': 0.0, - 'is_recommended': True, - 'player_behind_schedule_count': 0, - }, + } ] } diff --git a/frontend/src/pages/tournaments/[id].tsx b/frontend/src/pages/tournaments/[id].tsx index a32a8a138..c16de2b10 100644 --- a/frontend/src/pages/tournaments/[id].tsx +++ b/frontend/src/pages/tournaments/[id].tsx @@ -12,6 +12,7 @@ import { getTournamentIdFromRouter, responseIsValid } from '../../components/uti import { SchedulerSettings } from '../../interfaces/match'; import { RoundInterface } from '../../interfaces/round'; import { StageWithStageItems, getActiveStages } from '../../interfaces/stage'; +import { StageItemWithRounds } from '../../interfaces/stage_item'; import { Tournament, getTournamentEndpoint } from '../../interfaces/tournament'; import { checkForAuthError, @@ -57,10 +58,12 @@ export default function TournamentPage() { if (isResponseValid) { [activeStage] = getActiveStages(swrStagesResponse); - if (activeStage != null && activeStage.rounds != null) { - const draftRounds = activeStage.rounds.filter((round: RoundInterface) => round.is_draft); - if (draftRounds != null && draftRounds.length > 0) { - [draftRound] = draftRounds; + if (activeStage != null && activeStage.stage_items != null) { + const draftRounds = activeStage.stage_items.map((stageItem: StageItemWithRounds) => + stageItem.rounds.filter((round: RoundInterface) => round.is_draft) + ); + if (draftRounds != null && draftRounds.length > 0 && draftRounds[0].length > 0) { + [[draftRound]] = draftRounds; } }