fix campaign tie loop + dynamic tie dialog

This commit is contained in:
Maxime Réaux 2026-02-23 11:37:50 +01:00
parent f339498f97
commit c9407f9407
5 changed files with 120 additions and 222 deletions

View file

@ -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)

View file

@ -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

View file

@ -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'<img src="{icon_path}" width="16" height="16"> 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'<img src="{icon_path}" width="16" height="16"> 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:

View file

@ -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()

View file

@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>477</width>
<height>174</height>
<width>481</width>
<height>155</height>
</rect>
</property>
<property name="windowTitle">
@ -20,8 +20,8 @@
<iconset>
<normaloff>../resources/warchron_logo.png</normaloff>../resources/warchron_logo.png</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="tieContext">
@ -52,101 +52,25 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QGroupBox" name="groupBox_1">
<property name="title">
<string>Player 1</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Spend token</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Remaining token(s)</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="tokenSpend_1">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="tokenCount_1">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Player 2</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Spend token</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Remaining token(s)</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QCheckBox" name="tokenSpend_2">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="tokenCount_2">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
<item row="1" column="0">
<widget class="QScrollArea" name="playersScrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>461</width>
<height>78</height>
</rect>
</property>
<layout class="QVBoxLayout" name="playersGridLayout"/>
</widget>
</widget>
</item>
<item>
<item row="2" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>