From e7d3b962cad93cd847907b5c5be33be65c38cbf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20R=C3=A9aux?= Date: Thu, 26 Feb 2026 14:57:47 +0100 Subject: [PATCH] fix war tie ranking with bonus campaign ranking --- src/warchron/controller/war_controller.py | 2 +- src/warchron/model/result_checker.py | 65 +++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/warchron/controller/war_controller.py b/src/warchron/controller/war_controller.py index 554630e..ac2f0b4 100644 --- a/src/warchron/controller/war_controller.py +++ b/src/warchron/controller/war_controller.py @@ -133,7 +133,6 @@ class WarController: RefreshScope.WARS_TREE, item_type=ItemType.WAR, item_id=war_id ) - # TODO fix ignored campaign tie-breaks def resolve_ties( self, war: War, contexts: List[TieContext] ) -> Dict[Tuple[ContextType, str, int | None], Dict[str, bool]]: @@ -150,6 +149,7 @@ class WarController: for pid in active ] counters = [war.get_influence_tokens(pid) for pid in active] + # TODO fix sorted participants included in tie-break after campaign ranking dialog = TieDialog( parent=self.app.view, players=players, diff --git a/src/warchron/model/result_checker.py b/src/warchron/model/result_checker.py index a8c7661..6430ef6 100644 --- a/src/warchron/model/result_checker.py +++ b/src/warchron/model/result_checker.py @@ -45,6 +45,39 @@ class ResultChecker: current_rank = 1 for vp in sorted_vps: participants = vp_buckets[vp] + if context_type == ContextType.WAR and len(participants) > 1: + subgroups = ResultChecker._secondary_sorting_war(war, participants) + for subgroup in subgroups: + # no tie if campaigns' rank is enough to sort + if len(subgroup) == 1: + ranking.append( + (current_rank, subgroup, {pid: 0 for pid in subgroup}) + ) + current_rank += 1 + continue + # normal tie-break if tie persists + if not TieResolver.is_tie_resolved( + war, context_type, context_id, vp + ): + ranking.append( + (current_rank, subgroup, {pid: 0 for pid in subgroup}) + ) + current_rank += 1 + continue + groups = TieResolver.rank_by_tokens( + war, + context_type, + context_id, + subgroup, + ) + tokens_spent = TieResolver.tokens_spent_map( + war, context_type, context_id, subgroup + ) + for group in groups: + group_tokens = {pid: tokens_spent[pid] for pid in group} + ranking.append((current_rank, group, group_tokens)) + current_rank += 1 + continue # no tie if len(participants) == 1 or not TieResolver.is_tie_resolved( war, context_type, context_id, vp @@ -69,3 +102,35 @@ class ResultChecker: ranking.append((current_rank, group, group_tokens)) current_rank += 1 return ranking + + @staticmethod + def _secondary_sorting_war( + war: War, + participants: List[str], + ) -> List[List[str]]: + from warchron.model.score_service import ScoreService + + rank_map: Dict[str, Tuple[int, ...]] = {} + for pid in participants: + ranks: List[int] = [] + for campaign in war.campaigns: + scores = ScoreService.compute_scores( + war, ContextType.CAMPAIGN, campaign.id + ) + ranking = ResultChecker.get_effective_ranking( + war, ContextType.CAMPAIGN, campaign.id, scores + ) + for rank, group, _ in ranking: + if pid in group: + ranks.append(rank) + break + rank_map[pid] = tuple(ranks) + sorted_items = sorted(rank_map.items(), key=lambda x: x[1]) + groups: List[List[str]] = [] + current_tuple = None + for pid, rank_tuple in sorted_items: + if rank_tuple != current_tuple: + groups.append([]) + current_tuple = rank_tuple + groups[-1].append(pid) + return groups