diff --git a/src/warchron/controller/app_controller.py b/src/warchron/controller/app_controller.py index 693cc60..fd47b91 100644 --- a/src/warchron/controller/app_controller.py +++ b/src/warchron/controller/app_controller.py @@ -26,7 +26,6 @@ class AppController: self.current_file: Path | None = None self.view.on_close_callback = self.on_app_close self.is_dirty: bool = False - self.players_stats_dirty = True self.__connect() self.navigation.refresh_players_view() self.navigation.refresh_wars_view() @@ -61,12 +60,6 @@ class AppController: self.view.on_add_item = self.add_item self.view.on_edit_item = self.edit_item self.view.on_delete_item = self.delete_item - self.view.tabWidget.currentChanged.connect(self.navigation.on_tab_changed) - - def mark_model_dirty(self, *, players_stats: bool = False) -> None: - self.is_dirty = True - if players_stats: - self.players_stats_dirty = True def on_app_close(self) -> bool: if self.is_dirty: @@ -242,11 +235,10 @@ class AppController: QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, ) if reply == QMessageBox.StandardButton.Yes: - if e.action: - e.action() + e.action() else: return - self.mark_model_dirty(players_stats=True) # participation may affect stats + self.is_dirty = True self.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS) def edit_item(self, item_type: str, item_id: str) -> None: @@ -298,8 +290,7 @@ class AppController: ) if reply == QMessageBox.StandardButton.Yes: try: - if e.action: - e.action() + e.action() except DomainError as inner: QMessageBox.warning( self.view, @@ -309,7 +300,7 @@ class AppController: return else: return - self.mark_model_dirty(players_stats=(item_type == ItemType.PLAYER)) + self.is_dirty = True self.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS) def delete_item(self, item_type: str, item_id: str) -> None: @@ -370,8 +361,7 @@ class AppController: ) if reply == QMessageBox.StandardButton.Yes: try: - if e.action: - e.action() + e.action() except DomainError as inner: QMessageBox.warning( self.view, @@ -381,5 +371,5 @@ class AppController: return else: return - self.mark_model_dirty(players_stats=True) # participation may affect stats + self.is_dirty = True self.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS) diff --git a/src/warchron/controller/campaign_controller.py b/src/warchron/controller/campaign_controller.py index 5ebc964..1775509 100644 --- a/src/warchron/controller/campaign_controller.py +++ b/src/warchron/controller/campaign_controller.py @@ -160,7 +160,7 @@ class CampaignController: str(e), ) return - self.app.mark_model_dirty(players_stats=True) + self.app.is_dirty = True self.app.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS) self.app.navigation.refresh_and_select( RefreshScope.WARS_TREE, item_type=ItemType.CAMPAIGN, item_id=campaign_id diff --git a/src/warchron/controller/dtos.py b/src/warchron/controller/dtos.py index d646954..cc9b211 100644 --- a/src/warchron/controller/dtos.py +++ b/src/warchron/controller/dtos.py @@ -8,12 +8,6 @@ from PyQt6.QtGui import QIcon class ParticipantOption: id: str name: str - wars_played: int | None = None - wars_won: int | None = None - campaigns_played: int | None = None - campaigns_won: int | None = None - battles_played: int | None = None - battles_won: int | None = None @dataclass(frozen=True, slots=True) @@ -141,6 +135,11 @@ class WarParticipantScoreDTO: objective_icons: Dict[str, QIcon] = field(default_factory=dict) +@dataclass +class TieDialogData: + title: str + + @dataclass class WarSettingsDTO: major_value: int diff --git a/src/warchron/controller/navigation_controller.py b/src/warchron/controller/navigation_controller.py index a6035bd..2a6aa47 100644 --- a/src/warchron/controller/navigation_controller.py +++ b/src/warchron/controller/navigation_controller.py @@ -6,11 +6,11 @@ if TYPE_CHECKING: from warchron.controller.app_controller import AppController from warchron.controller.dtos import ( TreeSelection, + ParticipantOption, WarDTO, CampaignDTO, RoundDTO, ) -from warchron.controller.dtos import ParticipantOption class NavigationController: @@ -23,6 +23,13 @@ class NavigationController: # Display methods + def refresh_players_view(self) -> None: + players = self.app.model.get_all_players() + players_for_display: List[ParticipantOption] = [ + ParticipantOption(id=p.id, name=p.name) for p in players + ] + self.app.view.display_players(players_for_display) + def refresh_wars_view(self) -> None: wars = self.app.model.get_all_wars() wars_dto: List[WarDTO] = [ @@ -67,31 +74,6 @@ class NavigationController: self.app.wars._fill_war_details(first_war.id) self.update_actions_state() - def on_tab_changed(self, index: int) -> None: - tab = self.app.view.get_current_tab() - if tab == "players" and self.app.players_stats_dirty: - self.refresh_players_view() - - def refresh_players_view(self) -> None: - players = self.app.model.get_all_players() - players_for_display: List[ParticipantOption] = [] - for p in players: - stats = self.app.model.get_player_stats(p.id) - players_for_display.append( - ParticipantOption( - id=p.id, - name=p.name, - wars_played=stats.wars_played, - wars_won=stats.wars_won, - campaigns_played=stats.campaigns_played, - campaigns_won=stats.campaigns_won, - battles_played=stats.battles_played, - battles_won=stats.battles_won, - ) - ) - self.app.players_stats_dirty = False - self.app.view.display_players(players_for_display) - def refresh(self, scope: RefreshScope) -> None: match scope: case RefreshScope.PLAYERS_LIST: diff --git a/src/warchron/controller/presenter.py b/src/warchron/controller/presenter.py index a7d8f44..d41a336 100644 --- a/src/warchron/controller/presenter.py +++ b/src/warchron/controller/presenter.py @@ -1,5 +1,4 @@ from typing import Dict -from dataclasses import dataclass from PyQt6.QtGui import QIcon @@ -12,20 +11,16 @@ from warchron.constants import ( ScoreKind, ) +from warchron.controller.dtos import TieDialogData from warchron.model.tiebreaking import TieContext, TieBreaker from warchron.model.war import War from warchron.model.campaign import Campaign from warchron.model.round import Round -from warchron.model.scoring import ParticipantScore, ScoreComputer +from warchron.model.scoring import ParticipantScore from warchron.model.checking import ResultChecker from warchron.model.exception import DomainError -@dataclass -class TieDialogData: - title: str - - class Presenter: @staticmethod @@ -35,16 +30,17 @@ class Presenter: campaign: Campaign | None = None, round: Round | None = None, ) -> TieDialogData: - if ctx.context_type in (ContextType.WAR, ContextType.CAMPAIGN): - rank = Presenter._get_tie_rank(war, ctx) - rank_label = Presenter._build_rank_label(rank) - level = str(ctx.context_type).capitalize() - if ctx.objective_id is not None: + # TODO display Nth place + if ctx.context_type == ContextType.WAR: + if ctx.objective_id: obj = war.objectives[ctx.objective_id] - return TieDialogData( - f"{level} objective tie for {rank_label} — {obj.name}" - ) - return TieDialogData(f"{level} tie for {rank_label}") + return TieDialogData(f"War objective tie — {obj.name}") + return TieDialogData("War tie") + if ctx.context_type == ContextType.CAMPAIGN: + if ctx.objective_id: + obj = war.objectives[ctx.objective_id] + return TieDialogData(f"Campaign objective tie — {obj.name}") + return TieDialogData("Campaign tie") if ctx.context_type == ContextType.BATTLE: if campaign: sector = campaign.sectors[ctx.context_id] @@ -181,57 +177,3 @@ class Presenter: compute_icon(battle.player_1_id), compute_icon(battle.player_2_id), ) - - @staticmethod - def _ordinal(n: int) -> str: - if 10 <= n % 100 <= 20: - suffix = "th" - else: - suffix = {1: "st", 2: "nd", 3: "rd"}.get(n % 10, "th") - return f"{n}{suffix}" - - @staticmethod - def _build_rank_label(rank: int | None) -> str: - if rank is None: - return "" - return f"{Presenter._ordinal(rank)} place" - - @staticmethod - def _get_tie_rank( - war: War, - ctx: TieContext, - ) -> int | None: - from warchron.model.checking import ResultChecker - - scores = ScoreComputer.compute_scores( - war, - ctx.context_type, - ctx.context_id, - ) - if ctx.objective_id is None: - - def value_getter(score: ParticipantScore) -> int: - return score.victory_points - - score_kind = ScoreKind.VP - else: - obj_id = ctx.objective_id - - def value_getter(score: ParticipantScore) -> int: - return score.narrative_points.get(obj_id, 0) - - score_kind = ScoreKind.NP - ranking = ResultChecker.get_effective_ranking( - war, - ctx.context_type, - ctx.context_id, - score_kind, - scores, - value_getter, - ctx.objective_id, - ) - tied = set(ctx.participants) - for rank, group, _ in ranking: - if tied.intersection(group): - return rank - return None diff --git a/src/warchron/controller/round_controller.py b/src/warchron/controller/round_controller.py index 25585a0..fb11b7b 100644 --- a/src/warchron/controller/round_controller.py +++ b/src/warchron/controller/round_controller.py @@ -186,38 +186,16 @@ class RoundController: camp = self.app.model.get_campaign_by_round(round_id) war = self.app.model.get_war_by_round(round_id) workflow = RoundClosureWorkflow(self.app) - confirmed = False - stop = False - while True: - try: - workflow.start(war, camp, rnd, confirmed) - break - except RequiresConfirmation as e: - reply = QMessageBox.question( - self.app.view, - "Confirm closing", - str(e), - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, - ) - if reply == QMessageBox.StandardButton.Yes: - if e.action: - e.action() - confirmed = True - continue - else: - stop = True - break - except DomainError as e: - QMessageBox.warning( - self.app.view, - "Closure forbidden", - str(e), - ) - stop = True - break - if stop: + try: + workflow.start(war, camp, rnd) + except DomainError as e: + QMessageBox.warning( + self.app.view, + "Closure forbidden", + str(e), + ) return - self.app.mark_model_dirty(players_stats=True) + self.app.is_dirty = True self.app.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS) self.app.navigation.refresh_and_select( RefreshScope.WARS_TREE, item_type=ItemType.ROUND, item_id=round_id @@ -256,11 +234,10 @@ class RoundController: QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, ) if reply == QMessageBox.StandardButton.Yes: - if e.action: - e.action() + e.action() else: return - self.app.mark_model_dirty() + self.app.is_dirty = True self.app.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS) self.app.navigation.refresh_and_select( RefreshScope.WARS_TREE, item_type=ItemType.ROUND, item_id=round_id diff --git a/src/warchron/controller/war_controller.py b/src/warchron/controller/war_controller.py index 8546e58..4b7c3ef 100644 --- a/src/warchron/controller/war_controller.py +++ b/src/warchron/controller/war_controller.py @@ -147,7 +147,7 @@ class WarController: str(e), ) return - self.app.mark_model_dirty(players_stats=True) + self.app.is_dirty = True self.app.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS) self.app.navigation.refresh_and_select( RefreshScope.WARS_TREE, item_type=ItemType.WAR, item_id=war_id @@ -221,11 +221,10 @@ class WarController: QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, ) if reply == QMessageBox.StandardButton.Yes: - if e.action: - e.action() + e.action() else: return - self.app.mark_model_dirty() + self.is_dirty = True self.app.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS) self.app.navigation.refresh_and_select( RefreshScope.WARS_TREE, item_type=ItemType.WAR, item_id=war_id diff --git a/src/warchron/controller/workflows.py b/src/warchron/controller/workflows.py index ce0cc19..ea0a4b3 100644 --- a/src/warchron/controller/workflows.py +++ b/src/warchron/controller/workflows.py @@ -18,10 +18,8 @@ class Workflow: class RoundClosureWorkflow(Workflow): - def start( - self, war: War, campaign: Campaign, round: Round, confirmed: bool = False - ) -> None: - Closer.check_round_closable(round, confirmed) + def start(self, war: War, campaign: Campaign, round: Round) -> None: + Closer.check_round_closable(round) ties = TieBreaker.find_battle_ties(war, round.id) while ties: for tie in ties: diff --git a/src/warchron/model/battle.py b/src/warchron/model/battle.py index d54caaf..02a9e90 100644 --- a/src/warchron/model/battle.py +++ b/src/warchron/model/battle.py @@ -73,14 +73,6 @@ class Battle: return raise DomainError("Battle has no available places") - def get_participants_ids(self) -> List[str]: - players: List[str] = [] - if self.player_1_id: - players.append(self.player_1_id) - if self.player_2_id: - players.append(self.player_2_id) - return players - def clear_battle_players(self) -> None: self.player_1_id = None self.player_2_id = None @@ -88,12 +80,6 @@ class Battle: def is_finished(self) -> bool: return self.winner_id is not None or self.is_draw() - def has_player(self) -> bool: - return self.player_1_id is not None or self.player_2_id is not None - - def is_complete(self) -> bool: - return self.player_1_id is not None and self.player_2_id is not None - def toDict(self) -> Dict[str, Any]: return { "sector_id": self.sector_id, diff --git a/src/warchron/model/closing.py b/src/warchron/model/closing.py index 7140ac8..0b70b39 100644 --- a/src/warchron/model/closing.py +++ b/src/warchron/model/closing.py @@ -1,7 +1,7 @@ from __future__ import annotations from warchron.constants import ContextType -from warchron.model.exception import ForbiddenOperation, RequiresConfirmation +from warchron.model.exception import ForbiddenOperation from warchron.model.war_event import InfluenceGained from warchron.model.war import War from warchron.model.campaign import Campaign @@ -14,19 +14,13 @@ class Closer: # Round methods @staticmethod - def check_round_closable(round: Round, confirmed: bool) -> None: + def check_round_closable(round: Round) -> None: if round.is_over: raise ForbiddenOperation("Round already closed") - if not confirmed: - if any(not bat.is_complete() for bat in round.battles.values()): - raise RequiresConfirmation( - "Battle(s) in this round miss player(s).\n" - "Do you want to continue?", - ) - if not round.all_battles_finished(): - raise ForbiddenOperation( - "All battles must be finished to close their round" - ) + if not round.all_battles_finished(): + raise ForbiddenOperation( + "All battles must be finished to close their round" + ) @staticmethod def apply_battle_outcomes(war: War, campaign: Campaign, battle: Battle) -> None: diff --git a/src/warchron/model/exception.py b/src/warchron/model/exception.py index 5c27295..26194e3 100644 --- a/src/warchron/model/exception.py +++ b/src/warchron/model/exception.py @@ -26,6 +26,6 @@ class DomainDecision(Exception): class RequiresConfirmation(DomainDecision): - def __init__(self, message: str, action: Callable[[], None] | None = None): + def __init__(self, message: str, action: Callable[[], None]): super().__init__(message) self.action = action diff --git a/src/warchron/model/model.py b/src/warchron/model/model.py index a122fe0..d1bab74 100644 --- a/src/warchron/model/model.py +++ b/src/warchron/model/model.py @@ -15,7 +15,6 @@ from warchron.model.sector import Sector from warchron.model.round import Round from warchron.model.choice import Choice from warchron.model.battle import Battle -from warchron.model.statistics import PlayerStats, StatisticsComputer class Model: @@ -99,9 +98,6 @@ class Model: ) del self.players[player_id] - def get_player_stats(self, player_id: str) -> PlayerStats: - return StatisticsComputer.compute_player_stats(self.wars, player_id) - # War methods def get_default_war_values(self) -> Dict[str, Any]: diff --git a/src/warchron/model/pairing.py b/src/warchron/model/pairing.py index 09d9d58..3c8efae 100644 --- a/src/warchron/model/pairing.py +++ b/src/warchron/model/pairing.py @@ -63,7 +63,10 @@ class Pairing: bat.set_winner(None) war.revert_choice_ties(round.id) - if any(bat.has_player() for bat in round.battles.values()): + if any( + bat.player_1_id is not None or bat.player_2_id is not None + for bat in round.battles.values() + ): raise RequiresConfirmation( "Battle(s) already have player(s) assigned for this round.\n" "Battle players will be cleared.\n" @@ -326,8 +329,6 @@ class Pairing: round: Round, remaining: List[str], ) -> None: - if not remaining: - return campaign = war.get_campaign_by_round(round.id) if campaign is None: raise DomainError("Campaign not found") diff --git a/src/warchron/model/round.py b/src/warchron/model/round.py index ce4c73b..4b1a630 100644 --- a/src/warchron/model/round.py +++ b/src/warchron/model/round.py @@ -166,6 +166,7 @@ class Round: return any(b.is_finished() for b in self.battles.values()) def all_battles_finished(self) -> bool: + # TODO exception for participant alone return all( b.winner_id is not None or b.is_draw() for b in self.battles.values() ) diff --git a/src/warchron/model/statistics.py b/src/warchron/model/statistics.py deleted file mode 100644 index c1bcb11..0000000 --- a/src/warchron/model/statistics.py +++ /dev/null @@ -1,101 +0,0 @@ -from __future__ import annotations -from typing import Dict -from dataclasses import dataclass - -from warchron.constants import ContextType, ScoreKind -from warchron.model.war import War -from warchron.model.scoring import ScoreComputer -from warchron.model.checking import ResultChecker - - -@dataclass -class PlayerStats: - wars_played: int = 0 - wars_won: int = 0 - campaigns_played: int = 0 - campaigns_won: int = 0 - battles_played: int = 0 - battles_won: int = 0 - - -class StatisticsComputer: - - @staticmethod - def compute_player_stats( - wars: Dict[str, War], - player_id: str, - ) -> PlayerStats: - stats = PlayerStats() - for war in wars.values(): - # --- WAR PARTICIPANT --- - war_part = next( - (wp for wp in war.participants.values() if wp.player_id == player_id), - None, - ) - if not war_part: - continue - stats.wars_played += 1 - if war.is_over: - scores = ScoreComputer.compute_scores(war, ContextType.WAR, war.id) - ranking = ResultChecker.get_effective_ranking( - war, - ContextType.WAR, - war.id, - ScoreKind.VP, - scores, - lambda s: s.victory_points, - ) - if ranking and war_part.id in ranking[0][1]: - stats.wars_won += 1 - # --- CAMPAIGNS --- - for campaign in war.campaigns: - campaign_part = next( - ( - cp - for cp in campaign.participants.values() - if cp.war_participant_id == war_part.id - ), - None, - ) - if not campaign_part: - continue - stats.campaigns_played += 1 - if campaign.is_over: - scores = ScoreComputer.compute_scores( - war, ContextType.CAMPAIGN, campaign.id - ) - ranking = ResultChecker.get_effective_ranking( - war, - ContextType.CAMPAIGN, - campaign.id, - ScoreKind.VP, - scores, - lambda s: s.victory_points, - ) - if ranking and war_part.id in ranking[0][1]: - stats.campaigns_won += 1 - # --- BATTLES --- - for rnd in campaign.rounds: - for battle in rnd.battles.values(): - if not battle.is_finished(): - continue - if campaign_part.id not in ( - battle.player_1_id, - battle.player_2_id, - ): - continue - stats.battles_played += 1 - base_winner = None - if battle.winner_id is not None: - base_winner = campaign.campaign_to_war_part_id( - battle.winner_id - ) - winner = ResultChecker.get_effective_winner_id( - war, - ContextType.BATTLE, - battle.sector_id, - base_winner, - ) - if winner == war_part.id: - stats.battles_won += 1 - return stats diff --git a/src/warchron/model/tiebreaking.py b/src/warchron/model/tiebreaking.py index a71767b..ed11ed0 100644 --- a/src/warchron/model/tiebreaking.py +++ b/src/warchron/model/tiebreaking.py @@ -63,9 +63,8 @@ class TieBreaker: for battle in round.battles.values(): if campaign is None: raise DomainError("No campaign for this battle tie") - if not battle.is_complete(): - continue - assert battle.player_1_id is not None and battle.player_2_id is not None + if battle.player_1_id is None or battle.player_2_id is None: + raise DomainError("Missing player(s) in this battle context.") p1_id = campaign.campaign_to_war_part_id(battle.player_1_id) p2_id = campaign.campaign_to_war_part_id(battle.player_2_id) if not battle.is_draw(): diff --git a/src/warchron/view/ui/ui_settings_dialog.py b/src/warchron/view/ui/ui_settings_dialog.py index d051846..bf0f161 100644 --- a/src/warchron/view/ui/ui_settings_dialog.py +++ b/src/warchron/view/ui/ui_settings_dialog.py @@ -13,7 +13,7 @@ class Ui_settingsDialog(object): def setupUi(self, settingsDialog): settingsDialog.setObjectName("settingsDialog") settingsDialog.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal) - settingsDialog.resize(621, 387) + settingsDialog.resize(661, 377) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/warchron_logo.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) settingsDialog.setWindowIcon(icon) @@ -24,8 +24,8 @@ class Ui_settingsDialog(object): font.setPointSize(10) self.groupBox_2.setFont(font) self.groupBox_2.setObjectName("groupBox_2") - self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_2) - self.verticalLayout_3.setObjectName("verticalLayout_3") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_2) + self.verticalLayout_2.setObjectName("verticalLayout_2") self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.label_5 = QtWidgets.QLabel(parent=self.groupBox_2) @@ -56,58 +56,25 @@ class Ui_settingsDialog(object): self.horizontalLayout.addWidget(self.label_15) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) self.horizontalLayout.addItem(spacerItem1) - self.verticalLayout_3.addLayout(self.horizontalLayout) - self.horizontalLayout_2 = QtWidgets.QHBoxLayout() - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.label_12 = QtWidgets.QLabel(parent=self.groupBox_2) - self.label_12.setObjectName("label_12") - self.horizontalLayout_2.addWidget(self.label_12) - self.pointsComboBox = QtWidgets.QComboBox(parent=self.groupBox_2) - self.pointsComboBox.setEnabled(False) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.pointsComboBox.sizePolicy().hasHeightForWidth()) - self.pointsComboBox.setSizePolicy(sizePolicy) - self.pointsComboBox.setObjectName("pointsComboBox") - self.pointsComboBox.addItem("") - self.pointsComboBox.addItem("") - self.horizontalLayout_2.addWidget(self.pointsComboBox) - spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.horizontalLayout_2.addItem(spacerItem2) - self.internalTiebreak = QtWidgets.QCheckBox(parent=self.groupBox_2) - self.internalTiebreak.setEnabled(False) - self.internalTiebreak.setText("") - self.internalTiebreak.setCheckable(True) - self.internalTiebreak.setChecked(True) - self.internalTiebreak.setObjectName("internalTiebreak") - self.horizontalLayout_2.addWidget(self.internalTiebreak) - self.label_20 = QtWidgets.QLabel(parent=self.groupBox_2) - self.label_20.setObjectName("label_20") - self.horizontalLayout_2.addWidget(self.label_20) - spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.horizontalLayout_2.addItem(spacerItem3) - self.verticalLayout_3.addLayout(self.horizontalLayout_2) + self.verticalLayout_2.addLayout(self.horizontalLayout) self.horizontalLayout_5 = QtWidgets.QHBoxLayout() self.horizontalLayout_5.setObjectName("horizontalLayout_5") - self.label_19 = QtWidgets.QLabel(parent=self.groupBox_2) - self.label_19.setObjectName("label_19") - self.horizontalLayout_5.addWidget(self.label_19) - self.rankingComboBox_2 = QtWidgets.QComboBox(parent=self.groupBox_2) - self.rankingComboBox_2.setEnabled(False) + self.label_12 = QtWidgets.QLabel(parent=self.groupBox_2) + self.label_12.setObjectName("label_12") + self.horizontalLayout_5.addWidget(self.label_12) + self.rankingComboBox = QtWidgets.QComboBox(parent=self.groupBox_2) + self.rankingComboBox.setEnabled(False) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.rankingComboBox_2.sizePolicy().hasHeightForWidth()) - self.rankingComboBox_2.setSizePolicy(sizePolicy) - self.rankingComboBox_2.setObjectName("rankingComboBox_2") - self.rankingComboBox_2.addItem("") - self.rankingComboBox_2.addItem("") - self.rankingComboBox_2.addItem("") - self.horizontalLayout_5.addWidget(self.rankingComboBox_2) - spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.horizontalLayout_5.addItem(spacerItem4) - self.verticalLayout_3.addLayout(self.horizontalLayout_5) + sizePolicy.setHeightForWidth(self.rankingComboBox.sizePolicy().hasHeightForWidth()) + self.rankingComboBox.setSizePolicy(sizePolicy) + self.rankingComboBox.setObjectName("rankingComboBox") + self.rankingComboBox.addItem("") + self.horizontalLayout_5.addWidget(self.rankingComboBox) + spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_5.addItem(spacerItem2) + self.verticalLayout_2.addLayout(self.horizontalLayout_5) self.verticalLayout_4.addWidget(self.groupBox_2) self.groupBox = QtWidgets.QGroupBox(parent=settingsDialog) font = QtGui.QFont() @@ -128,8 +95,8 @@ class Ui_settingsDialog(object): self.label_8 = QtWidgets.QLabel(parent=self.groupBox) self.label_8.setObjectName("label_8") self.horizontalLayout_3.addWidget(self.label_8) - spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.horizontalLayout_3.addItem(spacerItem5) + spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_3.addItem(spacerItem3) self.label_9 = QtWidgets.QLabel(parent=self.groupBox) self.label_9.setObjectName("label_9") self.horizontalLayout_3.addWidget(self.label_9) @@ -140,8 +107,8 @@ class Ui_settingsDialog(object): self.label_10 = QtWidgets.QLabel(parent=self.groupBox) self.label_10.setObjectName("label_10") self.horizontalLayout_3.addWidget(self.label_10) - spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.horizontalLayout_3.addItem(spacerItem6) + spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_3.addItem(spacerItem4) self.verticalLayout.addLayout(self.horizontalLayout_3) self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") @@ -154,8 +121,8 @@ class Ui_settingsDialog(object): self.influenceToken.setChecked(True) self.influenceToken.setObjectName("influenceToken") self.horizontalLayout_4.addWidget(self.influenceToken) - spacerItem7 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.horizontalLayout_4.addItem(spacerItem7) + spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_4.addItem(spacerItem5) self.verticalLayout.addLayout(self.horizontalLayout_4) self.verticalLayout_4.addWidget(self.groupBox) self.groupBox_3 = QtWidgets.QGroupBox(parent=settingsDialog) @@ -163,45 +130,13 @@ class Ui_settingsDialog(object): font.setPointSize(10) self.groupBox_3.setFont(font) self.groupBox_3.setObjectName("groupBox_3") - self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_3) - self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_3) + self.verticalLayout_3.setObjectName("verticalLayout_3") self.horizontalLayout_7 = QtWidgets.QHBoxLayout() self.horizontalLayout_7.setObjectName("horizontalLayout_7") - self.label_21 = QtWidgets.QLabel(parent=self.groupBox_3) - self.label_21.setObjectName("label_21") - self.horizontalLayout_7.addWidget(self.label_21) - self.drawComboBox = QtWidgets.QComboBox(parent=self.groupBox_3) - self.drawComboBox.setEnabled(False) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.drawComboBox.sizePolicy().hasHeightForWidth()) - self.drawComboBox.setSizePolicy(sizePolicy) - self.drawComboBox.setObjectName("drawComboBox") - self.drawComboBox.addItem("") - self.drawComboBox.addItem("") - self.drawComboBox.addItem("") - self.horizontalLayout_7.addWidget(self.drawComboBox) - spacerItem8 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.horizontalLayout_7.addItem(spacerItem8) - self.label_18 = QtWidgets.QLabel(parent=self.groupBox_3) - self.label_18.setObjectName("label_18") - self.horizontalLayout_7.addWidget(self.label_18) - self.shuffle = QtWidgets.QCheckBox(parent=self.groupBox_3) - self.shuffle.setEnabled(False) - self.shuffle.setText("") - self.shuffle.setCheckable(True) - self.shuffle.setChecked(True) - self.shuffle.setObjectName("shuffle") - self.horizontalLayout_7.addWidget(self.shuffle) - spacerItem9 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.horizontalLayout_7.addItem(spacerItem9) - self.verticalLayout_2.addLayout(self.horizontalLayout_7) - self.horizontalLayout_8 = QtWidgets.QHBoxLayout() - self.horizontalLayout_8.setObjectName("horizontalLayout_8") self.label_17 = QtWidgets.QLabel(parent=self.groupBox_3) self.label_17.setObjectName("label_17") - self.horizontalLayout_8.addWidget(self.label_17) + self.horizontalLayout_7.addWidget(self.label_17) self.fallbackComboBox = QtWidgets.QComboBox(parent=self.groupBox_3) self.fallbackComboBox.setEnabled(False) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed) @@ -211,11 +146,22 @@ class Ui_settingsDialog(object): self.fallbackComboBox.setSizePolicy(sizePolicy) self.fallbackComboBox.setObjectName("fallbackComboBox") self.fallbackComboBox.addItem("") - self.fallbackComboBox.addItem("") - self.horizontalLayout_8.addWidget(self.fallbackComboBox) - spacerItem10 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.horizontalLayout_8.addItem(spacerItem10) - self.verticalLayout_2.addLayout(self.horizontalLayout_8) + self.horizontalLayout_7.addWidget(self.fallbackComboBox) + spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_7.addItem(spacerItem6) + self.label_18 = QtWidgets.QLabel(parent=self.groupBox_3) + self.label_18.setObjectName("label_18") + self.horizontalLayout_7.addWidget(self.label_18) + self.influenceToken_2 = QtWidgets.QCheckBox(parent=self.groupBox_3) + self.influenceToken_2.setEnabled(False) + self.influenceToken_2.setText("") + self.influenceToken_2.setCheckable(True) + self.influenceToken_2.setChecked(True) + self.influenceToken_2.setObjectName("influenceToken_2") + self.horizontalLayout_7.addWidget(self.influenceToken_2) + spacerItem7 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_7.addItem(spacerItem7) + self.verticalLayout_3.addLayout(self.horizontalLayout_7) self.horizontalLayout_6 = QtWidgets.QHBoxLayout() self.horizontalLayout_6.setObjectName("horizontalLayout_6") self.label_13 = QtWidgets.QLabel(parent=self.groupBox_3) @@ -226,8 +172,8 @@ class Ui_settingsDialog(object): self.rematchValue.setMinimum(1) self.rematchValue.setObjectName("rematchValue") self.horizontalLayout_6.addWidget(self.rematchValue) - spacerItem11 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.horizontalLayout_6.addItem(spacerItem11) + spacerItem8 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_6.addItem(spacerItem8) self.label_16 = QtWidgets.QLabel(parent=self.groupBox_3) self.label_16.setObjectName("label_16") self.horizontalLayout_6.addWidget(self.label_16) @@ -236,9 +182,9 @@ class Ui_settingsDialog(object): self.occupancyValue.setMinimum(1) self.occupancyValue.setObjectName("occupancyValue") self.horizontalLayout_6.addWidget(self.occupancyValue) - spacerItem12 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.horizontalLayout_6.addItem(spacerItem12) - self.verticalLayout_2.addLayout(self.horizontalLayout_6) + spacerItem9 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_6.addItem(spacerItem9) + self.verticalLayout_3.addLayout(self.horizontalLayout_6) self.verticalLayout_4.addWidget(self.groupBox_3) self.buttonBox = QtWidgets.QDialogButtonBox(parent=settingsDialog) self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) @@ -259,14 +205,8 @@ class Ui_settingsDialog(object): self.label_14.setText(_translate("settingsDialog", "victory points")) self.label_6.setText(_translate("settingsDialog", "Draw")) self.label_15.setText(_translate("settingsDialog", "victory points")) - self.label_12.setText(_translate("settingsDialog", "War points mode")) - self.pointsComboBox.setItemText(0, _translate("settingsDialog", "Sum battle points")) - self.pointsComboBox.setItemText(1, _translate("settingsDialog", "Sum campaign ranking")) - self.label_20.setText(_translate("settingsDialog", "Count internal tie-breaks")) - self.label_19.setText(_translate("settingsDialog", "Ranking mode")) - self.rankingComboBox_2.setItemText(0, _translate("settingsDialog", "Dense (1-2-2-3)")) - self.rankingComboBox_2.setItemText(1, _translate("settingsDialog", "Shift-up (1-2-2-4)")) - self.rankingComboBox_2.setItemText(2, _translate("settingsDialog", "Shift-down (1-3-3-4)")) + self.label_12.setText(_translate("settingsDialog", "Ranking mode")) + self.rankingComboBox.setItemText(0, _translate("settingsDialog", "Sum points & tie-breaks")) self.groupBox.setTitle(_translate("settingsDialog", "Objectives")) self.label_4.setText(_translate("settingsDialog", "Major objective")) self.label_8.setText(_translate("settingsDialog", "narrative points")) @@ -275,14 +215,9 @@ class Ui_settingsDialog(object): self.label_11.setText(_translate("settingsDialog", "Underlying influence")) self.influenceToken.setText(_translate("settingsDialog", "Token")) self.groupBox_3.setTitle(_translate("settingsDialog", "Pairing")) - self.label_21.setText(_translate("settingsDialog", "Draw priority")) - self.drawComboBox.setItemText(0, _translate("settingsDialog", "Best war ranking")) - self.drawComboBox.setItemText(1, _translate("settingsDialog", "Random")) - self.drawComboBox.setItemText(2, _translate("settingsDialog", "Avoid rematch")) - self.label_18.setText(_translate("settingsDialog", "Shuffle groups")) self.label_17.setText(_translate("settingsDialog", "Fallback mode")) - self.fallbackComboBox.setItemText(0, _translate("settingsDialog", "Avoid rematch")) - self.fallbackComboBox.setItemText(1, _translate("settingsDialog", "Random")) + self.fallbackComboBox.setItemText(0, _translate("settingsDialog", "Best ranking first & avoid rematch")) + self.label_18.setText(_translate("settingsDialog", "Shuffle")) self.label_13.setText(_translate("settingsDialog", "Rematch weight")) self.label_16.setText(_translate("settingsDialog", "Occupancy weight")) diff --git a/src/warchron/view/ui/ui_settings_dialog.ui b/src/warchron/view/ui/ui_settings_dialog.ui index cc251f0..2b371bc 100644 --- a/src/warchron/view/ui/ui_settings_dialog.ui +++ b/src/warchron/view/ui/ui_settings_dialog.ui @@ -9,8 +9,8 @@ 0 0 - 621 - 387 + 661 + 377 @@ -31,7 +31,7 @@ Scores - + @@ -116,100 +116,17 @@ - - - - - - War points mode - - - - - - - false - - - - 0 - 0 - - - - - Sum battle points - - - - - Sum campaign ranking - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - - - - true - - - true - - - - - - - Count internal tie-breaks - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - + Ranking mode - + false @@ -221,23 +138,13 @@ - Dense (1-2-2-3) - - - - - Shift-up (1-2-2-4) - - - - - Shift-down (1-3-3-4) + Sum points & tie-breaks - + Qt::Horizontal @@ -390,97 +297,9 @@ Pairing - + - - - - Draw priority - - - - - - - false - - - - 0 - 0 - - - - - Best war ranking - - - - - Random - - - - - Avoid rematch - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Shuffle groups - - - - - - - false - - - - - - true - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - @@ -501,12 +320,7 @@ - Avoid rematch - - - - - Random + Best ranking first & avoid rematch @@ -524,6 +338,42 @@ + + + + Shuffle + + + + + + + false + + + + + + true + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + diff --git a/src/warchron/view/ui/ui_tie_dialog.py b/src/warchron/view/ui/ui_tie_dialog.py index f6af573..0361373 100644 --- a/src/warchron/view/ui/ui_tie_dialog.py +++ b/src/warchron/view/ui/ui_tie_dialog.py @@ -67,7 +67,7 @@ class Ui_tieDialog(object): def retranslateUi(self, tieDialog): _translate = QtCore.QCoreApplication.translate - tieDialog.setWindowTitle(_translate("tieDialog", "Tie-break")) + tieDialog.setWindowTitle(_translate("tieDialog", "Tie")) self.tieContext.setText(_translate("tieDialog", "Battle tie")) diff --git a/src/warchron/view/ui/ui_tie_dialog.ui b/src/warchron/view/ui/ui_tie_dialog.ui index 69b9fef..38e6744 100644 --- a/src/warchron/view/ui/ui_tie_dialog.ui +++ b/src/warchron/view/ui/ui_tie_dialog.ui @@ -14,7 +14,7 @@ - Tie-break + Tie diff --git a/src/warchron/view/view.py b/src/warchron/view/view.py index d6a8709..c34077d 100644 --- a/src/warchron/view/view.py +++ b/src/warchron/view/view.py @@ -200,47 +200,15 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): elif action == delete_action and self.on_delete_item: self.on_delete_item(ItemType.PLAYER, player_id) - def _make_ratio_item( - self, won: int | None, played: int | None - ) -> QtWidgets.QTableWidgetItem: - if not played: - text = "—" - ratio = -1.0 - else: - won = won or 0 - text = f"{won} / {played}" - ratio = won / played - item = QtWidgets.QTableWidgetItem(text) - item.setData(Qt.ItemDataRole.UserRole, ratio) - return item - def display_players(self, players: List[ParticipantOption]) -> None: + # TODO display stats (war, campaign battles...) table = self.playersTable table.setSortingEnabled(False) - table.setColumnCount(4) - table.setHorizontalHeaderLabels(["Name", "Wars", "Campaigns", "Battles"]) table.setRowCount(len(players)) for row, player in enumerate(players): play_item = QtWidgets.QTableWidgetItem(player.name) - wars_item = self._make_ratio_item( - player.wars_won, - player.wars_played, - ) - - camp_item = self._make_ratio_item( - player.campaigns_won, - player.campaigns_played, - ) - - bat_item = self._make_ratio_item( - player.battles_won, - player.battles_played, - ) play_item.setData(Qt.ItemDataRole.UserRole, player.id) table.setItem(row, 0, play_item) - table.setItem(row, 1, wars_item) - table.setItem(row, 2, camp_item) - table.setItem(row, 3, bat_item) table.setSortingEnabled(True) table.sortItems(0, Qt.SortOrder.AscendingOrder) table.resizeColumnsToContents()