diff --git a/src/warchron/constants.py b/src/warchron/constants.py index 241ff64..8da62f9 100644 --- a/src/warchron/constants.py +++ b/src/warchron/constants.py @@ -15,8 +15,6 @@ RESOURCES_DIR = VIEW_ROOT / "resources" ROLE_TYPE = Qt.ItemDataRole.UserRole ROLE_ID = Qt.ItemDataRole.UserRole + 1 -# TODO use StrEnum and auto() instead of str,Enum and "name" - class IconName(str, Enum): UNDO = "undo" @@ -64,30 +62,15 @@ class IconName(str, Enum): VPNTHDRAW = auto() VPNTHBREAK = auto() VPNTHTIEDRAW = auto() - NP1STDRAW = auto() - NP1STBREAK = auto() - NP1STTIEDRAW = auto() - NP2NDDRAW = auto() - NP2NDBREAK = auto() - NP2NDTIEDRAW = auto() - NP3RDDRAW = auto() - NP3RDBREAK = auto() - NP3RDTIEDRAW = auto() -VP_RANK_TO_ICON = { +RANK_TO_ICON = { 1: IconName.VP1ST, 2: IconName.VP2ND, 3: IconName.VP3RD, 4: IconName.VPNTH, } -NP_RANK_TO_ICON = { - 1: IconName.NP1ST, - 2: IconName.NP2ND, - 3: IconName.NP3RD, -} - class Icons: _icon_cache: Dict[IconName, QIcon] = {} @@ -209,54 +192,6 @@ class Icons: cls.get_pixmap(IconName.DRAW), cls.get_pixmap(IconName.TOKEN), ) - elif name == IconName.NP1STDRAW: - pix = cls._compose( - cls.get_pixmap(IconName.NP1ST), - cls.get_pixmap(IconName.DRAW), - ) - elif name == IconName.NP1STBREAK: - pix = cls._compose( - cls.get_pixmap(IconName.NP1ST), - cls.get_pixmap(IconName.TOKEN), - ) - elif name == IconName.NP1STTIEDRAW: - pix = cls._compose( - cls.get_pixmap(IconName.NP1ST), - cls.get_pixmap(IconName.DRAW), - cls.get_pixmap(IconName.TOKEN), - ) - elif name == IconName.NP2NDDRAW: - pix = cls._compose( - cls.get_pixmap(IconName.NP2ND), - cls.get_pixmap(IconName.DRAW), - ) - elif name == IconName.NP2NDBREAK: - pix = cls._compose( - cls.get_pixmap(IconName.NP2ND), - cls.get_pixmap(IconName.TOKEN), - ) - elif name == IconName.NP2NDTIEDRAW: - pix = cls._compose( - cls.get_pixmap(IconName.NP2ND), - cls.get_pixmap(IconName.DRAW), - cls.get_pixmap(IconName.TOKEN), - ) - elif name == IconName.NP3RDDRAW: - pix = cls._compose( - cls.get_pixmap(IconName.NP3RD), - cls.get_pixmap(IconName.DRAW), - ) - elif name == IconName.NP3RDBREAK: - pix = cls._compose( - cls.get_pixmap(IconName.NP3RD), - cls.get_pixmap(IconName.TOKEN), - ) - elif name == IconName.NP3RDTIEDRAW: - pix = cls._compose( - cls.get_pixmap(IconName.NP3RD), - cls.get_pixmap(IconName.DRAW), - cls.get_pixmap(IconName.TOKEN), - ) else: path = RESOURCES_DIR / cls._paths[name] pix = QPixmap(path.as_posix()) @@ -310,4 +245,3 @@ class ContextType(StrEnum): CAMPAIGN = "campaign" CHOICE = "choice" BATTLE = "battle" - OBJECTIVE = auto() diff --git a/src/warchron/controller/campaign_controller.py b/src/warchron/controller/campaign_controller.py index 00ffa3d..88199eb 100644 --- a/src/warchron/controller/campaign_controller.py +++ b/src/warchron/controller/campaign_controller.py @@ -1,7 +1,6 @@ from typing import List, Dict, Tuple, TYPE_CHECKING from PyQt6.QtWidgets import QMessageBox, QDialog -from PyQt6.QtGui import QIcon from warchron.constants import ( RefreshScope, @@ -58,30 +57,16 @@ class CampaignController: self.app.view.display_campaign_sectors(sectors_for_display) scores = ScoreService.compute_scores(war, ContextType.CAMPAIGN, campaign_id) rows: List[CampaignParticipantScoreDTO] = [] - vp_icon_map: Dict[str, QIcon] = {} - objective_icon_maps: Dict[str, Dict[str, QIcon]] = {} + icon_map = {} if camp.is_over: - vp_icon_map = RankingIcon.compute_icons( + icon_map = RankingIcon.compute_icons( war, ContextType.CAMPAIGN, campaign_id, scores ) - for obj in war.get_all_objectives(): - objective_icon_maps[obj.id] = RankingIcon.compute_icons( - war, - ContextType.CAMPAIGN, - f"{camp.id}:{obj.id}", - scores, - objective_id=obj.id, - ) for camp_part in camp.get_all_campaign_participants(): war_part_id = camp_part.war_participant_id war_part = war.get_war_participant(war_part_id) player_name = self.app.model.get_player_name(war_part.player_id) score = scores[war_part_id] - objective_icons = { - obj_id: icon_map[war_part_id] - for obj_id, icon_map in objective_icon_maps.items() - if war_part_id in icon_map - } rows.append( CampaignParticipantScoreDTO( campaign_participant_id=camp_part.id, @@ -92,8 +77,7 @@ class CampaignController: victory_points=score.victory_points, narrative_points=dict(score.narrative_points), tokens=war.get_influence_tokens(war_part.id), - rank_icon=vp_icon_map.get(war_part_id), - objective_icons=objective_icons, + rank_icon=icon_map.get(war_part_id), ) ) objectives = [ @@ -187,19 +171,7 @@ class CampaignController: counters=counters, context_type=ContextType.CAMPAIGN, context_id=ctx.context_id, - context_name=None, ) - if ctx.context_type == ContextType.OBJECTIVE: - campaign_id, objective_id = ctx.context_id.split(":") - objective = war.objectives[objective_id] - dialog = TieDialog( - parent=self.app.view, - players=players, - counters=counters, - context_type=ctx.context_type, - context_id=ctx.context_id, - context_name=objective.name, - ) if not dialog.exec(): TieResolver.cancel_tie_break( war, ContextType.CAMPAIGN, ctx.context_id, ctx.score_value diff --git a/src/warchron/controller/closure_workflow.py b/src/warchron/controller/closure_workflow.py index e54f921..fe368ac 100644 --- a/src/warchron/controller/closure_workflow.py +++ b/src/warchron/controller/closure_workflow.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from warchron.controller.app_controller import AppController + from warchron.model.war import War from warchron.model.campaign import Campaign from warchron.model.round import Round @@ -44,23 +45,6 @@ class CampaignClosureWorkflow(ClosureWorkflow): TieResolver.apply_bids(war, tie.context_type, tie.context_id, bids) TieResolver.resolve_tie_state(war, tie, bids) ties = TieResolver.find_campaign_ties(war, campaign.id) - for objective_id in war.objectives: - ties = TieResolver.find_campaign_objective_ties( - war, - campaign.id, - objective_id, - ) - while ties: - bids_map = self.app.campaigns.resolve_ties(war, ties) - for tie in ties: - bids = bids_map[(tie.context_type, tie.context_id, tie.score_value)] - TieResolver.apply_bids(war, tie.context_type, tie.context_id, bids) - TieResolver.resolve_tie_state(war, tie, bids) - ties = TieResolver.find_campaign_objective_ties( - war, - campaign.id, - objective_id, - ) ClosureService.finalize_campaign(campaign) @@ -76,19 +60,4 @@ class WarClosureWorkflow(ClosureWorkflow): TieResolver.apply_bids(war, tie.context_type, tie.context_id, bids) TieResolver.resolve_tie_state(war, tie, bids) ties = TieResolver.find_war_ties(war) - for objective_id in war.objectives: - ties = TieResolver.find_war_objective_ties( - war, - objective_id, - ) - while ties: - bids_map = self.app.wars.resolve_ties(war, ties) - for tie in ties: - bids = bids_map[(tie.context_type, tie.context_id, tie.score_value)] - TieResolver.apply_bids(war, tie.context_type, tie.context_id, bids) - TieResolver.resolve_tie_state(war, tie, bids) - ties = TieResolver.find_war_objective_ties( - war, - objective_id, - ) ClosureService.finalize_war(war) diff --git a/src/warchron/controller/dtos.py b/src/warchron/controller/dtos.py index 9451bc6..dcbf05e 100644 --- a/src/warchron/controller/dtos.py +++ b/src/warchron/controller/dtos.py @@ -1,5 +1,5 @@ from typing import List, Dict -from dataclasses import dataclass, field +from dataclasses import dataclass from PyQt6.QtGui import QIcon @@ -107,6 +107,14 @@ class BattleDTO: player2_tooltip: str | None = None +@dataclass(frozen=True, slots=True) +class ParticipantScoreDTO: + participant_id: str + player_name: str + victory_points: int + narrative_points: Dict[str, int] + + @dataclass(frozen=True, slots=True) class CampaignParticipantScoreDTO: campaign_participant_id: str @@ -118,7 +126,6 @@ class CampaignParticipantScoreDTO: narrative_points: Dict[str, int] tokens: int rank_icon: QIcon | None = None - objective_icons: Dict[str, QIcon] = field(default_factory=dict) @dataclass(frozen=True, slots=True) @@ -131,4 +138,3 @@ class WarParticipantScoreDTO: narrative_points: Dict[str, int] tokens: int rank_icon: QIcon | None = None - objective_icons: Dict[str, QIcon] = field(default_factory=dict) diff --git a/src/warchron/controller/ranking_icon.py b/src/warchron/controller/ranking_icon.py index 27024d5..bb7d914 100644 --- a/src/warchron/controller/ranking_icon.py +++ b/src/warchron/controller/ranking_icon.py @@ -6,8 +6,7 @@ from warchron.constants import ( ContextType, Icons, IconName, - VP_RANK_TO_ICON, - NP_RANK_TO_ICON, + RANK_TO_ICON, ) from warchron.model.war import War from warchron.model.score_service import ParticipantScore @@ -21,37 +20,21 @@ class RankingIcon: context_type: ContextType, context_id: str, scores: Dict[str, ParticipantScore], - *, - objective_id: str | None = None, ) -> Dict[str, QIcon]: - - if objective_id is None: - - def value_getter(score: ParticipantScore) -> int: - return score.victory_points - - icon_ranking = VP_RANK_TO_ICON - else: - - def value_getter(score: ParticipantScore) -> int: - return score.narrative_points.get(objective_id, 0) - - icon_ranking = NP_RANK_TO_ICON + # scores = ScoreService.compute_scores( + # war, + # context_type, + # context_id, + # ) ranking = ResultChecker.get_effective_ranking( - war, context_type, context_id, scores, value_getter=value_getter + war, context_type, context_id, scores ) - icon_map: Dict[str, QIcon] = {} + icon_map = {} for rank, group, token_map in ranking: - if objective_id and rank not in icon_ranking: - continue - base_icon = icon_ranking.get( - rank, IconName.VPNTH if objective_id is None else None - ) - if base_icon is None: - continue - value = value_getter(scores[group[0]]) + base_icon = RANK_TO_ICON.get(rank, IconName.VPNTH) + vp = scores[group[0]].victory_points original_group_size = sum( - 1 for s in scores.values() if value_getter(s) == value + 1 for s in scores.values() if s.victory_points == vp ) for pid in group: spent = token_map.get(pid, 0) diff --git a/src/warchron/controller/war_controller.py b/src/warchron/controller/war_controller.py index ddec9ff..84977e9 100644 --- a/src/warchron/controller/war_controller.py +++ b/src/warchron/controller/war_controller.py @@ -1,7 +1,6 @@ from typing import List, Tuple, TYPE_CHECKING, Dict from PyQt6.QtWidgets import QMessageBox, QDialog -from PyQt6.QtGui import QIcon from warchron.constants import ( RefreshScope, @@ -54,28 +53,12 @@ class WarController: self.app.view.display_war_objectives(objectives_for_display) scores = ScoreService.compute_scores(war, ContextType.WAR, war.id) rows: List[WarParticipantScoreDTO] = [] - vp_icon_map: dict[str, QIcon] = {} - objective_icon_maps: Dict[str, Dict[str, QIcon]] = {} + icon_map = {} if war.is_over: - vp_icon_map = RankingIcon.compute_icons( - war, ContextType.WAR, war_id, scores - ) - for obj in war.get_all_objectives(): - objective_icon_maps[obj.id] = RankingIcon.compute_icons( - war, - ContextType.WAR, - f"{war.id}:{obj.id}", - scores, - objective_id=obj.id, - ) + icon_map = RankingIcon.compute_icons(war, ContextType.WAR, war_id, scores) for war_part in war.get_all_war_participants(): player_name = self.app.model.get_player_name(war_part.player_id) score = scores[war_part.id] - objective_icons = { - obj_id: icon_map[war_part.id] - for obj_id, icon_map in objective_icon_maps.items() - if war_part.id in icon_map - } rows.append( WarParticipantScoreDTO( war_participant_id=war_part.id, @@ -85,8 +68,7 @@ class WarController: victory_points=score.victory_points, narrative_points=dict(score.narrative_points), tokens=war.get_influence_tokens(war_part.id), - rank_icon=vp_icon_map.get(war_part.id), - objective_icons=objective_icons, + rank_icon=icon_map.get(war_part.id), ) ) self.app.view.display_war_participants(rows, objectives_for_display) @@ -151,7 +133,6 @@ class WarController: RefreshScope.WARS_TREE, item_type=ItemType.WAR, item_id=war_id ) - # FIXME tie dialog with all participant even without tie def resolve_ties( self, war: War, contexts: List[TieContext] ) -> Dict[Tuple[ContextType, str, int | None], Dict[str, bool]]: @@ -174,19 +155,7 @@ class WarController: counters=counters, context_type=ContextType.WAR, context_id=ctx.context_id, - context_name=None, ) - if ctx.context_type == ContextType.OBJECTIVE: - _, objective_id = ctx.context_id.split(":") - objective = war.objectives[objective_id] - dialog = TieDialog( - parent=self.app.view, - players=players, - counters=counters, - context_type=ctx.context_type, - context_id=ctx.context_id, - context_name=objective.name, - ) if not dialog.exec(): TieResolver.cancel_tie_break( war, ContextType.WAR, ctx.context_id, ctx.score_value diff --git a/src/warchron/model/result_checker.py b/src/warchron/model/result_checker.py index b06ecb8..6430ef6 100644 --- a/src/warchron/model/result_checker.py +++ b/src/warchron/model/result_checker.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import List, Tuple, Dict, TYPE_CHECKING, Callable +from typing import List, Tuple, Dict, TYPE_CHECKING from collections import defaultdict from warchron.constants import ContextType @@ -36,16 +36,15 @@ class ResultChecker: context_type: ContextType, context_id: str, scores: Dict[str, ParticipantScore], - value_getter: Callable[[ParticipantScore], int], ) -> List[Tuple[int, List[str], Dict[str, int]]]: - buckets: Dict[int, List[str]] = defaultdict(list) + vp_buckets: Dict[int, List[str]] = defaultdict(list) for pid, score in scores.items(): - buckets[value_getter(score)].append(pid) - sorted_vps = sorted(buckets.keys(), reverse=True) + vp_buckets[score.victory_points].append(pid) + sorted_vps = sorted(vp_buckets.keys(), reverse=True) ranking: List[Tuple[int, List[str], Dict[str, int]]] = [] current_rank = 1 - for value in sorted_vps: - participants = buckets[value] + 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: @@ -58,7 +57,7 @@ class ResultChecker: continue # normal tie-break if tie persists if not TieResolver.is_tie_resolved( - war, context_type, context_id, value + war, context_type, context_id, vp ): ranking.append( (current_rank, subgroup, {pid: 0 for pid in subgroup}) @@ -81,7 +80,7 @@ class ResultChecker: continue # no tie if len(participants) == 1 or not TieResolver.is_tie_resolved( - war, context_type, context_id, value + war, context_type, context_id, vp ): ranking.append( (current_rank, participants, {pid: 0 for pid in participants}) @@ -119,11 +118,7 @@ class ResultChecker: war, ContextType.CAMPAIGN, campaign.id ) ranking = ResultChecker.get_effective_ranking( - war, - ContextType.CAMPAIGN, - campaign.id, - scores, - lambda s: s.victory_points, + war, ContextType.CAMPAIGN, campaign.id, scores ) for rank, group, _ in ranking: if pid in group: diff --git a/src/warchron/model/tie_manager.py b/src/warchron/model/tie_manager.py index 13a220b..e174e8e 100644 --- a/src/warchron/model/tie_manager.py +++ b/src/warchron/model/tie_manager.py @@ -6,7 +6,7 @@ from warchron.constants import ContextType from warchron.model.exception import ForbiddenOperation from warchron.model.war import War from warchron.model.war_event import InfluenceSpent, TieResolved -from warchron.model.score_service import ScoreService, ParticipantScore +from warchron.model.score_service import ScoreService @dataclass @@ -85,72 +85,13 @@ class TieResolver: ) return ties - @staticmethod - def find_campaign_objective_ties( - war: War, - campaign_id: str, - objective_id: str, - ) -> List[TieContext]: - base_scores = ScoreService.compute_scores( - war, - ContextType.CAMPAIGN, - campaign_id, - ) - scores = TieResolver._build_objective_scores( - base_scores, - objective_id, - ) - buckets: DefaultDict[int, List[str]] = defaultdict(list) - for pid, score in scores.items(): - buckets[score.victory_points].append(pid) - ties: List[TieContext] = [] - context_id = f"{campaign_id}:{objective_id}" - for score_value, participants in buckets.items(): - if len(participants) <= 1: - continue - if TieResolver.is_tie_resolved( - war, - ContextType.OBJECTIVE, - context_id, - score_value, - ): - continue - if not TieResolver.can_tie_be_resolved( - war, - ContextType.OBJECTIVE, - context_id, - participants, - ): - war.events.append( - TieResolved( - None, - ContextType.OBJECTIVE, - context_id, - score_value, - ) - ) - continue - ties.append( - TieContext( - context_type=ContextType.OBJECTIVE, - context_id=context_id, - participants=participants, - score_value=score_value, - ) - ) - return ties - @staticmethod def find_war_ties(war: War) -> List[TieContext]: from warchron.model.result_checker import ResultChecker scores = ScoreService.compute_scores(war, ContextType.WAR, war.id) ranking = ResultChecker.get_effective_ranking( - war, - ContextType.WAR, - war.id, - scores, - value_getter=lambda s: s.victory_points, + war, ContextType.WAR, war.id, scores ) ties: List[TieContext] = [] for _, group, _ in ranking: @@ -174,80 +115,6 @@ class TieResolver: ) return ties - @staticmethod - def find_war_objective_ties( - war: War, - objective_id: str, - ) -> List[TieContext]: - from warchron.model.result_checker import ResultChecker - - base_scores = ScoreService.compute_scores( - war, - ContextType.WAR, - war.id, - ) - scores = TieResolver._build_objective_scores( - base_scores, - objective_id, - ) - ranking = ResultChecker.get_effective_ranking( - war, - ContextType.OBJECTIVE, - f"{war.id}:{objective_id}", - scores, - value_getter=lambda s: s.narrative_points.get(objective_id, 0), - ) - ties: List[TieContext] = [] - for _, group, _ in ranking: - if len(group) <= 1: - continue - score_value = scores[group[0]].victory_points - context_id = f"{war.id}:{objective_id}" - if TieResolver.is_tie_resolved( - war, - ContextType.OBJECTIVE, - context_id, - score_value, - ): - continue - if not TieResolver.can_tie_be_resolved( - war, - ContextType.OBJECTIVE, - context_id, - group, - ): - war.events.append( - TieResolved( - None, - ContextType.OBJECTIVE, - context_id, - score_value, - ) - ) - continue - ties.append( - TieContext( - context_type=ContextType.OBJECTIVE, - context_id=context_id, - participants=group, - score_value=score_value, - ) - ) - return ties - - @staticmethod - def _build_objective_scores( - base_scores: Dict[str, ParticipantScore], - objective_id: str, - ) -> Dict[str, ParticipantScore]: - return { - pid: ParticipantScore( - victory_points=score.narrative_points.get(objective_id, 0), - narrative_points={}, - ) - for pid, score in base_scores.items() - } - @staticmethod def apply_bids( war: War, diff --git a/src/warchron/view/tie_dialog.py b/src/warchron/view/tie_dialog.py index 41a17ff..971a5ec 100644 --- a/src/warchron/view/tie_dialog.py +++ b/src/warchron/view/tie_dialog.py @@ -26,7 +26,6 @@ class TieDialog(QDialog): counters: List[int], context_type: ContextType, context_id: str, - context_name: str | None = None, ) -> None: super().__init__(parent) self._context_id = context_id @@ -34,7 +33,7 @@ class TieDialog(QDialog): self.ui: Ui_tieDialog = Ui_tieDialog() self.ui.setupUi(self) # type: ignore self.setWindowIcon(Icons.get(IconName.WARCHRONICO)) - self.ui.tieContext.setText(self._get_context_title(context_type, context_name)) + self.ui.tieContext.setText(self._get_context_title(context_type)) grid = self.ui.playersGridLayout icon_path = (RESOURCES_DIR / Icons._paths[IconName.TOKENS]).as_posix() token_html = ( @@ -73,14 +72,11 @@ class TieDialog(QDialog): return {pid: checkbox.isChecked() for pid, checkbox in self._checkboxes.items()} @staticmethod - def _get_context_title( - context_type: ContextType, context_name: str | None = None - ) -> str: + def _get_context_title(context_type: ContextType) -> str: titles = { ContextType.BATTLE: "Battle tie", ContextType.CAMPAIGN: "Campaign tie", ContextType.WAR: "War tie", ContextType.CHOICE: "Choice tie", - ContextType.OBJECTIVE: f"Objective tie: {context_name}", } return titles.get(context_type, "Tie") diff --git a/src/warchron/view/view.py b/src/warchron/view/view.py index 160719a..012c2bf 100644 --- a/src/warchron/view/view.py +++ b/src/warchron/view/view.py @@ -413,8 +413,6 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): for obj in objectives: value = part.narrative_points.get(obj.id, 0) NP_item = QtWidgets.QTableWidgetItem(str(value)) - if part.objective_icons.get(obj.id): - NP_item.setIcon(part.objective_icons[obj.id]) table.setItem(row, col, NP_item) col += 1 table.setItem(row, col, token_item) @@ -547,8 +545,6 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): for obj in objectives: value = part.narrative_points.get(obj.id, 0) NP_item = QtWidgets.QTableWidgetItem(str(value)) - if part.objective_icons.get(obj.id): - NP_item.setIcon(part.objective_icons[obj.id]) table.setItem(row, col, NP_item) col += 1 table.setItem(row, col, token_item)