From c9407f940741b76cc93a7798c73c90160b761a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20R=C3=A9aux?= Date: Mon, 23 Feb 2026 11:37:50 +0100 Subject: [PATCH] fix campaign tie loop + dynamic tie dialog --- src/warchron/controller/closure_workflow.py | 19 +--- src/warchron/model/tie_manager.py | 33 +++--- src/warchron/view/tie_dialog.py | 68 +++++++---- src/warchron/view/ui/ui_tie_dialog.py | 102 ++++++----------- src/warchron/view/ui/ui_tie_dialog.ui | 120 ++++---------------- 5 files changed, 120 insertions(+), 222 deletions(-) diff --git a/src/warchron/controller/closure_workflow.py b/src/warchron/controller/closure_workflow.py index 16e0de3..01076d2 100644 --- a/src/warchron/controller/closure_workflow.py +++ b/src/warchron/controller/closure_workflow.py @@ -25,12 +25,7 @@ class RoundClosureWorkflow(ClosureWorkflow): bids_map = self.app.rounds.resolve_ties(war, ties) for tie in ties: bids = bids_map[tie.context_id] - TieResolver.apply_bids( - war, - tie.context_type, - tie.context_id, - bids, - ) + TieResolver.apply_bids(war, tie.context_type, tie.context_id, bids) TieResolver.resolve_tie_state( war, tie.context_type, tie.context_id, tie.participants, bids ) @@ -49,17 +44,9 @@ class CampaignClosureWorkflow(ClosureWorkflow): bids_map = self.app.campaigns.resolve_ties(war, ties) for tie in ties: bids = bids_map[tie.context_id] - TieResolver.apply_bids( - war, - tie.context_type, - tie.context_id, - bids, - ) + TieResolver.apply_bids(war, tie.context_type, tie.context_id, bids) TieResolver.resolve_tie_state( - war, - tie.context_type, - tie.context_id, - tie.participants, + war, tie.context_type, tie.context_id, tie.participants, bids ) ties = TieResolver.find_campaign_ties(war, campaign.id) ClosureService.finalize_campaign(campaign) diff --git a/src/warchron/model/tie_manager.py b/src/warchron/model/tie_manager.py index f9c4054..75cacdb 100644 --- a/src/warchron/model/tie_manager.py +++ b/src/warchron/model/tie_manager.py @@ -16,14 +16,6 @@ class TieContext: participants: List[str] # war_participant_ids -@dataclass -class TieState: - winner: str | None - tied_players: List[str] - eliminated: List[str] - # is_final: bool - - class TieResolver: @staticmethod @@ -65,16 +57,23 @@ class TieResolver: buckets: DefaultDict[int, List[str]] = defaultdict(list) for pid, score in scores.items(): buckets[score.victory_points].append(pid) - ties = [] - for participants in buckets.values(): - if len(participants) > 1: - ties.append( - TieContext( - context_type=ContextType.CAMPAIGN, - context_id=campaign_id, - participants=participants, - ) + ties: List[TieContext] = [] + for score_value, participants in buckets.items(): + if len(participants) <= 1: + continue + tie_id = f"{campaign_id}:score:{score_value}" + if TieResolver.is_tie_resolved(war, ContextType.CAMPAIGN, tie_id): + continue + if not TieResolver.can_tie_be_resolved(war, participants): + war.events.append(TieResolved(None, ContextType.CAMPAIGN, tie_id)) + continue + ties.append( + TieContext( + context_type=ContextType.CAMPAIGN, + context_id=tie_id, + participants=participants, ) + ) return ties @staticmethod diff --git a/src/warchron/view/tie_dialog.py b/src/warchron/view/tie_dialog.py index 201ea32..2e75e48 100644 --- a/src/warchron/view/tie_dialog.py +++ b/src/warchron/view/tie_dialog.py @@ -1,6 +1,15 @@ from typing import List, Dict -from PyQt6.QtWidgets import QWidget, QDialog +from PyQt6.QtWidgets import ( + QWidget, + QDialog, + QCheckBox, + QGroupBox, + QHBoxLayout, + QVBoxLayout, + QLabel, + QLineEdit, +) from PyQt6.QtCore import Qt from warchron.constants import Icons, IconName, ContextType, RESOURCES_DIR @@ -20,32 +29,47 @@ class TieDialog(QDialog): ) -> None: super().__init__(parent) self._context_id = context_id - self._p1_id = players[0].id - self._p2_id = players[1].id + self._checkboxes: Dict[str, QCheckBox] = {} self.ui: Ui_tieDialog = Ui_tieDialog() self.ui.setupUi(self) # type: ignore - self.ui.tieContext.setText(self._get_context_title(context_type)) - icon_path = (RESOURCES_DIR / Icons._paths[IconName.TOKENS]).as_posix() - html = f' Remaining token(s)' - self.ui.label_2.setText(html) - self.ui.label_2.setTextFormat(Qt.TextFormat.RichText) - self.ui.label_3.setText(html) - self.ui.label_3.setTextFormat(Qt.TextFormat.RichText) - self.ui.groupBox_1.setTitle(players[0].name) - self.ui.groupBox_2.setTitle(players[1].name) - self.ui.tokenCount_1.setText(str(counters[0])) - self.ui.tokenCount_2.setText(str(counters[1])) - if counters[0] < 1: - self.ui.tokenSpend_1.setDisabled(True) - if counters[1] < 1: - self.ui.tokenSpend_2.setDisabled(True) self.setWindowIcon(Icons.get(IconName.WARCHRON)) + 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 = ( + f' Remaining token(s)' + ) + for i, (player, tokens) in enumerate(zip(players, counters)): + group = QGroupBox(player.name) + row_layout = QHBoxLayout() + left = QVBoxLayout() + spend_label = QLabel("Spend token") + token_label = QLabel(token_html) + token_label.setTextFormat(Qt.TextFormat.RichText) + left.addWidget(spend_label) + left.addWidget(token_label) + right = QVBoxLayout() + checkbox = QCheckBox() + count = QLineEdit(str(tokens)) + count.setEnabled(False) + if tokens < 1: + checkbox.setDisabled(True) + right.addWidget(checkbox) + right.addWidget(count) + row_layout.addLayout(left) + row_layout.addLayout(right) + group.setLayout(row_layout) + row = i // 2 + col = i % 2 + grid.addWidget(group, row, col) + self._checkboxes[player.id] = checkbox + grid.setColumnStretch(0, 1) + grid.setColumnStretch(1, 1) + self.ui.playersScrollArea.setMinimumHeight(110) + self.adjustSize() def get_bids(self) -> Dict[str, bool]: - return { - self._p1_id: self.ui.tokenSpend_1.isChecked(), - self._p2_id: self.ui.tokenSpend_2.isChecked(), - } + return {pid: checkbox.isChecked() for pid, checkbox in self._checkboxes.items()} @staticmethod def _get_context_title(context_type: ContextType) -> str: diff --git a/src/warchron/view/ui/ui_tie_dialog.py b/src/warchron/view/ui/ui_tie_dialog.py index 1bfc512..0361373 100644 --- a/src/warchron/view/ui/ui_tie_dialog.py +++ b/src/warchron/view/ui/ui_tie_dialog.py @@ -13,12 +13,16 @@ class Ui_tieDialog(object): def setupUi(self, tieDialog): tieDialog.setObjectName("tieDialog") tieDialog.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal) - tieDialog.resize(477, 174) + tieDialog.resize(481, 155) icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/warchron_logo.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) + icon.addPixmap( + QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/warchron_logo.png"), + QtGui.QIcon.Mode.Normal, + QtGui.QIcon.State.Off, + ) tieDialog.setWindowIcon(icon) - self.verticalLayout_5 = QtWidgets.QVBoxLayout(tieDialog) - self.verticalLayout_5.setObjectName("verticalLayout_5") + self.gridLayout = QtWidgets.QGridLayout(tieDialog) + self.gridLayout.setObjectName("gridLayout") self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.tieContext = QtWidgets.QLabel(parent=tieDialog) @@ -29,87 +33,47 @@ class Ui_tieDialog(object): self.tieContext.setFont(font) self.tieContext.setObjectName("tieContext") self.horizontalLayout_3.addWidget(self.tieContext) - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + spacerItem = QtWidgets.QSpacerItem( + 40, + 20, + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Minimum, + ) self.horizontalLayout_3.addItem(spacerItem) - self.verticalLayout_5.addLayout(self.horizontalLayout_3) - self.horizontalLayout_4 = QtWidgets.QHBoxLayout() - self.horizontalLayout_4.setObjectName("horizontalLayout_4") - self.groupBox_1 = QtWidgets.QGroupBox(parent=tieDialog) - self.groupBox_1.setObjectName("groupBox_1") - self.horizontalLayout = QtWidgets.QHBoxLayout(self.groupBox_1) - self.horizontalLayout.setObjectName("horizontalLayout") - self.verticalLayout = QtWidgets.QVBoxLayout() - self.verticalLayout.setObjectName("verticalLayout") - self.label_5 = QtWidgets.QLabel(parent=self.groupBox_1) - self.label_5.setObjectName("label_5") - self.verticalLayout.addWidget(self.label_5) - self.label_2 = QtWidgets.QLabel(parent=self.groupBox_1) - self.label_2.setObjectName("label_2") - self.verticalLayout.addWidget(self.label_2) - self.horizontalLayout.addLayout(self.verticalLayout) - self.verticalLayout_2 = QtWidgets.QVBoxLayout() - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.tokenSpend_1 = QtWidgets.QCheckBox(parent=self.groupBox_1) - self.tokenSpend_1.setText("") - self.tokenSpend_1.setObjectName("tokenSpend_1") - self.verticalLayout_2.addWidget(self.tokenSpend_1) - self.tokenCount_1 = QtWidgets.QLineEdit(parent=self.groupBox_1) - self.tokenCount_1.setEnabled(False) - self.tokenCount_1.setObjectName("tokenCount_1") - self.verticalLayout_2.addWidget(self.tokenCount_1) - self.horizontalLayout.addLayout(self.verticalLayout_2) - self.horizontalLayout_4.addWidget(self.groupBox_1) - self.groupBox_2 = QtWidgets.QGroupBox(parent=tieDialog) - self.groupBox_2.setObjectName("groupBox_2") - self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.groupBox_2) - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.verticalLayout_3 = QtWidgets.QVBoxLayout() - self.verticalLayout_3.setObjectName("verticalLayout_3") - self.label_6 = QtWidgets.QLabel(parent=self.groupBox_2) - self.label_6.setObjectName("label_6") - self.verticalLayout_3.addWidget(self.label_6) - self.label_3 = QtWidgets.QLabel(parent=self.groupBox_2) - self.label_3.setObjectName("label_3") - self.verticalLayout_3.addWidget(self.label_3) - self.horizontalLayout_2.addLayout(self.verticalLayout_3) - self.verticalLayout_4 = QtWidgets.QVBoxLayout() - self.verticalLayout_4.setObjectName("verticalLayout_4") - self.tokenSpend_2 = QtWidgets.QCheckBox(parent=self.groupBox_2) - self.tokenSpend_2.setText("") - self.tokenSpend_2.setObjectName("tokenSpend_2") - self.verticalLayout_4.addWidget(self.tokenSpend_2) - self.tokenCount_2 = QtWidgets.QLineEdit(parent=self.groupBox_2) - self.tokenCount_2.setEnabled(False) - self.tokenCount_2.setObjectName("tokenCount_2") - self.verticalLayout_4.addWidget(self.tokenCount_2) - self.horizontalLayout_2.addLayout(self.verticalLayout_4) - self.horizontalLayout_4.addWidget(self.groupBox_2) - self.verticalLayout_5.addLayout(self.horizontalLayout_4) + self.gridLayout.addLayout(self.horizontalLayout_3, 0, 0, 1, 1) + self.playersScrollArea = QtWidgets.QScrollArea(parent=tieDialog) + self.playersScrollArea.setWidgetResizable(True) + self.playersScrollArea.setObjectName("playersScrollArea") + self.scrollAreaWidgetContents = QtWidgets.QWidget() + self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 461, 78)) + self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") + self.playersGridLayout = QtWidgets.QGridLayout(self.scrollAreaWidgetContents) + self.playersGridLayout.setObjectName("playersGridLayout") + self.playersScrollArea.setWidget(self.scrollAreaWidgetContents) + self.gridLayout.addWidget(self.playersScrollArea, 1, 0, 1, 1) self.buttonBox = QtWidgets.QDialogButtonBox(parent=tieDialog) self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) - self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok) + self.buttonBox.setStandardButtons( + QtWidgets.QDialogButtonBox.StandardButton.Cancel + | QtWidgets.QDialogButtonBox.StandardButton.Ok + ) self.buttonBox.setObjectName("buttonBox") - self.verticalLayout_5.addWidget(self.buttonBox) + self.gridLayout.addWidget(self.buttonBox, 2, 0, 1, 1) self.retranslateUi(tieDialog) - self.buttonBox.accepted.connect(tieDialog.accept) # type: ignore - self.buttonBox.rejected.connect(tieDialog.reject) # type: ignore + self.buttonBox.accepted.connect(tieDialog.accept) # type: ignore + self.buttonBox.rejected.connect(tieDialog.reject) # type: ignore QtCore.QMetaObject.connectSlotsByName(tieDialog) def retranslateUi(self, tieDialog): _translate = QtCore.QCoreApplication.translate tieDialog.setWindowTitle(_translate("tieDialog", "Tie")) self.tieContext.setText(_translate("tieDialog", "Battle tie")) - self.groupBox_1.setTitle(_translate("tieDialog", "Player 1")) - self.label_5.setText(_translate("tieDialog", "Spend token")) - self.label_2.setText(_translate("tieDialog", "Remaining token(s)")) - self.groupBox_2.setTitle(_translate("tieDialog", "Player 2")) - self.label_6.setText(_translate("tieDialog", "Spend token")) - self.label_3.setText(_translate("tieDialog", "Remaining token(s)")) if __name__ == "__main__": import sys + app = QtWidgets.QApplication(sys.argv) tieDialog = QtWidgets.QDialog() ui = Ui_tieDialog() diff --git a/src/warchron/view/ui/ui_tie_dialog.ui b/src/warchron/view/ui/ui_tie_dialog.ui index ccceb54..38e6744 100644 --- a/src/warchron/view/ui/ui_tie_dialog.ui +++ b/src/warchron/view/ui/ui_tie_dialog.ui @@ -9,8 +9,8 @@ 0 0 - 477 - 174 + 481 + 155 @@ -20,8 +20,8 @@ ../resources/warchron_logo.png../resources/warchron_logo.png - - + + @@ -52,101 +52,25 @@ - - - - - - Player 1 - - - - - - - - Spend token - - - - - - - Remaining token(s) - - - - - - - - - - - - - - - - - - false - - - - - - - - - - - - Player 2 - - - - - - - - Spend token - - - - - - - Remaining token(s) - - - - - - - - - - - - - - - - - - false - - - - - - - - - + + + + true + + + + + 0 + 0 + 461 + 78 + + + + + - + Qt::Horizontal