From 48ad1b6e7f00186c71ca5fbd64573f264edfae9c Mon Sep 17 00:00:00 2001 From: sidhdhi canopas Date: Thu, 21 Nov 2024 13:13:38 +0530 Subject: [PATCH] add hybrid and full bestOf approach --- data/lib/api/match/match_model.dart | 18 ++ .../match_selection/match_scheduler.dart | 197 +++++++++++++++++- 2 files changed, 214 insertions(+), 1 deletion(-) diff --git a/data/lib/api/match/match_model.dart b/data/lib/api/match/match_model.dart index 8d9ffe8d..b9e790c0 100644 --- a/data/lib/api/match/match_model.dart +++ b/data/lib/api/match/match_model.dart @@ -1,6 +1,7 @@ // ignore_for_file: non_constant_identifier_names import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:collection/collection.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import '../../converter/timestamp_json_converter.dart'; @@ -210,6 +211,23 @@ extension DataMatchModel on MatchModel { revised_target == null && match_type == MatchType.limitedOvers; } + + double getRunRate(String teamId) { + if (!team_ids.contains(teamId)) return 0; + + final team = teams.firstWhereOrNull((team) => team.team_id == teamId); + if (team == null) return 0; + + final runs = team.run; + final overs = team.over; + + if (overs == 0) { + return 0.0; + } + + final runRate = runs / overs; + return double.parse(runRate.toStringAsFixed(2)); + } } enum WinnerByType { diff --git a/khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart b/khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart index 380bd823..397f94a1 100644 --- a/khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart +++ b/khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart @@ -62,7 +62,8 @@ class MatchScheduler { case TournamentType.doubleOut: return scheduleDoubleOutMatches(); case TournamentType.bestOf: - return scheduleBestOfMatches(); + // return scheduleBestOfMatchesLoopingApproach(); + return scheduleBestOfMatchesHybridApproach(); case TournamentType.custom: return scheduledMatches; } @@ -340,6 +341,80 @@ class MatchScheduler { return additionalScheduledMatches; } + GroupedMatchMap scheduleBestOfMatchesLoopingApproach() { + final GroupedMatchMap additionalScheduledMatches = + Map.from(scheduledMatches); + final List teamPool = List.of(teams); + + var currentGroup = MatchGroup.round; + var currentRound = 1; + + while (teamPool.length > 1) { + final group = + additionalScheduledMatches.putIfAbsent(currentGroup, () => {1: []}); + final matches = group[currentRound] ?? []; + final expectedQualifiers = handleSingleBestOfThreePhase( + matches, teamPool, currentGroup, currentRound); + group[currentRound] = matches; + + if (expectedQualifiers > 8) { + currentRound++; + } else if (expectedQualifiers > 4) { + currentRound = 1; + currentGroup = MatchGroup.quarterfinal; + } else if (expectedQualifiers > 2) { + currentRound = 1; + currentGroup = MatchGroup.semifinal; + } else if (expectedQualifiers == 2) { + currentRound = 1; + currentGroup = MatchGroup.finals; + } + } + + return additionalScheduledMatches; + } + + GroupedMatchMap scheduleBestOfMatchesHybridApproach() { + final GroupedMatchMap additionalScheduledMatches = + Map.from(scheduledMatches); + final List teamPool = List.of(teams); + + var currentGroup = MatchGroup.round; + var currentRound = 1; + + while (teamPool.length > 1) { + final group = + additionalScheduledMatches.putIfAbsent(currentGroup, () => {1: []}); + final matches = group[currentRound] ?? []; + var expectedQualifiers = 0; + + if (currentGroup == MatchGroup.round) { + expectedQualifiers = handleSingleBestOfThreePhase( + matches, teamPool, currentGroup, currentRound); + } else { + expectedQualifiers = + handleSingleKnockoutPhase(matches, teamPool, currentGroup, currentRound); + } + + group[currentRound] = matches; + + if (expectedQualifiers > 8) { + currentRound++; + } else if (expectedQualifiers > 4) { + currentRound = 1; + currentGroup = MatchGroup.quarterfinal; + } else if (expectedQualifiers > 2) { + currentRound = 1; + currentGroup = MatchGroup.semifinal; + } else if (expectedQualifiers == 2) { + currentRound = 1; + currentGroup = MatchGroup.finals; + } + } + + return additionalScheduledMatches; + } + List> createTeamPairsForBestOfThree(List teams) { final List> pairs = []; for (int i = 0; i < teams.length; i++) { @@ -714,4 +789,124 @@ class MatchScheduler { } return teamToReturn; } + + int handleSingleKnockoutPhase(List matches, List teamPool, + MatchGroup group, int number) { + removeAlreadyScheduledTeams(matches, teamPool); + + final teamPairs = createKnockoutTeamPairs(teamPool); + addMatches(matches, teamPairs, group, number); + + teamPool.removeWhere((team) => teamPairs + .any((element) => element.length == 2 && element.contains(team))); + + addWinnerTeamsBackToTeam(matches, teamPool); + + return matches.length; + } + + int handleSingleBestOfThreePhase( + List matches, List teamPool, group, number) { + final existingPairs = getExistingTeamPairs(matches); + final remainedTeams = teamPool + .where((team) => !existingPairs.expand((pair) => pair).contains(team)) + .toList(); + + final newPairs = createKnockoutTeamPairs(remainedTeams); + final allPairs = [...existingPairs, ...newPairs]; + + final winners = + handleBestOfThreeMatchesAndItsWinner(matches, allPairs, group, number); + + teamPool.removeWhere((team) => allPairs.any((pair) => + pair.length == 2 && pair.contains(team) && !winners.contains(team.id))); + return allPairs.length; + } + + List> getExistingTeamPairs(List matches) { + final List> pairs = []; + for (var match in matches) { + final pair = match.teams.map((e) => e.team).toList(); + if (!pairs.map((e) => e.map((e) => e.id)).any((element) => + element.contains(pair.first.id) && element.contains(pair.last.id))) { + pairs.add(pair); + } + } + + return pairs; + } + + List handleBestOfThreeMatchesAndItsWinner( + List groupMatches, + List> pair, + MatchGroup group, + int number, + ) { + final List winnerTeams = []; + for (var p in pair) { + final matchesForPair = groupMatches + .where( + (element) => + element.team_ids.contains(p.first.id) && + element.team_ids.contains(p.last.id), + ) + .toList(); + if (matchesForPair.isEmpty) { + addMatches(groupMatches, [p], group, number); + addMatches(groupMatches, [p], group, number); + } else if (matchesForPair.length == 1) { + addMatches(groupMatches, [p], group, number); + } else { + final finishedMatches = + matchesForPair.where((element) => element.matchResult != null); + if (finishedMatches.length >= 2) { + final winner = getWinnerTeam( + matchesForPair.toList(), + p.first.id, + p.last.id, + ); + if (winner == null && matchesForPair.length == 2) { + addMatches(groupMatches, [p], group, number); + } else if (winner != null) { + winnerTeams.add(winner); + } + } + } + } + return winnerTeams; + } + + String? getWinnerTeam( + List matches, + String teamOne, + String teamTwo, + ) { + Map winner = {teamOne: 0, teamTwo: 0}; + for (var element in matches) { + final result = element.matchResult; + if (result == null) continue; + + if (result.winType != WinnerByType.tie && + winner.containsKey(result.teamId)) { + winner[result.teamId] = winner[result.teamId]! + 1; + } else if (result.winType == WinnerByType.tie) { + final runRateOne = element.getRunRate(teamOne); + final runRateTwo = element.getRunRate(teamTwo); + final win = (runRateOne > runRateTwo ? teamOne : teamTwo); + if (winner.containsKey(win)) { + winner[win] = winner[win]! + 1; + } + } + } + final maxWin = winner.values.max; + + final maxWinners = + winner.entries.where((element) => element.value == maxWin); + + if (maxWinners.length == 1) { + return maxWinners.first.key; + } else { + return null; + } + } }