tie dialog title with context details

This commit is contained in:
Maxime Réaux 2026-03-18 14:30:57 +01:00
parent aa75a5b84f
commit 9e602e8ca4
8 changed files with 109 additions and 46 deletions

View file

@ -23,6 +23,7 @@ from warchron.model.tie_manager import TieContext, TieResolver
from warchron.model.score_service import ScoreService
from warchron.controller.closure_workflow import CampaignClosureWorkflow
from warchron.controller.ranking_icon import RankingIcon
from warchron.controller.presenter import TiePresenter
from warchron.view.campaign_dialog import CampaignDialog
from warchron.view.campaign_participant_dialog import CampaignParticipantDialog
from warchron.view.sector_dialog import SectorDialog
@ -176,24 +177,17 @@ class CampaignController:
for pid in active
]
counters = [war.get_influence_tokens(pid) for pid in active]
data = TiePresenter.build_dialog_data(
war, ctx, campaign=war.get_campaign(ctx.context_id)
)
dialog = TieDialog(
parent=self.app.view,
players=players,
counters=counters,
context_type=ctx.context_type,
context_id=ctx.context_id,
context_name=None,
context_name=data.title,
)
if ctx.objective_id:
objective = war.objectives[ctx.objective_id]
dialog = TieDialog(
parent=self.app.view,
players=players,
counters=counters,
context_type=ctx.context_type,
context_id=ctx.context_id,
context_name=f"Objective tie: {objective.name}",
)
if not dialog.exec():
TieResolver.cancel_tie_break(war, ctx)
raise AbortedOperation("Tie resolution cancelled")

View file

@ -132,3 +132,8 @@ class WarParticipantScoreDTO:
tokens: int
rank_icon: QIcon | None = None
objective_icons: Dict[str, QIcon] = field(default_factory=dict)
@dataclass
class TieDialogData:
title: str

View file

@ -0,0 +1,52 @@
from warchron.constants import ContextType
from warchron.controller.dtos import TieDialogData
from warchron.model.tie_manager import TieContext
from warchron.model.war import War
from warchron.model.campaign import Campaign
from warchron.model.round import Round
class TiePresenter:
@staticmethod
def build_dialog_data(
war: War,
ctx: TieContext,
campaign: Campaign | None = None,
round: Round | None = None,
) -> TieDialogData:
if ctx.context_type == ContextType.WAR:
if ctx.objective_id:
obj = war.objectives[ctx.objective_id]
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]
return TieDialogData(f"Battle tie — {sector.name}")
return TieDialogData("Battle tie")
if ctx.context_type == ContextType.CHOICE:
if ctx.sector_id and campaign and round:
sector = campaign.sectors[ctx.sector_id]
kind = TiePresenter._choice_kind(round, ctx)
return TieDialogData(f"Choice tie — {sector.name} ({kind})")
return TieDialogData("Choice tie")
return TieDialogData("Tie")
@staticmethod
def _choice_kind(round: Round, ctx: TieContext) -> str:
for pid in ctx.participants:
camp_pid = round.campaign.war_to_campaign_part_id(pid)
choice = round.choices.get(camp_pid)
if not choice:
continue
if choice.priority_sector_id == ctx.sector_id:
return "priority"
if choice.secondary_sector_id == ctx.sector_id:
return "secondary"
return "choice"

View file

@ -28,6 +28,7 @@ from warchron.controller.closure_workflow import (
RoundClosureWorkflow,
RoundPairingWorkflow,
)
from warchron.controller.presenter import TiePresenter
from warchron.view.choice_dialog import ChoiceDialog
from warchron.view.battle_dialog import BattleDialog
from warchron.view.tie_dialog import TieDialog
@ -243,13 +244,26 @@ class RoundController:
for pid in ctx.participants
]
counters = [war.get_influence_tokens(pid) for pid in ctx.participants]
# TODO display sector name for BATTLE or CHOICE
if ctx.context_type == ContextType.BATTLE:
# context_id = battle.sector_id
campaign = war.get_campaign_by_sector(ctx.context_id)
if campaign:
round = campaign.get_round_by_battle(ctx.context_id)
if ctx.context_type == ContextType.CHOICE:
# context_id = round.id
campaign = war.get_campaign_by_round(ctx.context_id)
if campaign:
round = war.get_round(ctx.context_id)
data = TiePresenter.build_dialog_data(
war, ctx, round=round, campaign=campaign
)
dialog = TieDialog(
parent=self.app.view,
players=players,
counters=counters,
context_type=ctx.context_type,
context_id=ctx.context_id,
context_name=data.title,
)
if not dialog.exec():
TieResolver.cancel_tie_break(war, ctx)

View file

@ -28,6 +28,7 @@ from warchron.model.tie_manager import TieContext, TieResolver
from warchron.model.score_service import ScoreService
from warchron.controller.closure_workflow import WarClosureWorkflow
from warchron.controller.ranking_icon import RankingIcon
from warchron.controller.presenter import TiePresenter
from warchron.view.war_dialog import WarDialog
from warchron.view.objective_dialog import ObjectiveDialog
from warchron.view.war_participant_dialog import WarParticipantDialog
@ -170,24 +171,18 @@ class WarController:
for pid in active
]
counters = [war.get_influence_tokens(pid) for pid in active]
data = TiePresenter.build_dialog_data(
war,
ctx,
)
dialog = TieDialog(
parent=self.app.view,
players=players,
counters=counters,
context_type=ctx.context_type,
context_id=ctx.context_id,
context_name=None,
context_name=data.title,
)
if ctx.objective_id:
objective = war.objectives[ctx.objective_id]
dialog = TieDialog(
parent=self.app.view,
players=players,
counters=counters,
context_type=ctx.context_type,
context_id=ctx.context_id,
context_name=f"Objective tie: {objective.name}",
)
if not dialog.exec():
TieResolver.cancel_tie_break(war, ctx)
raise AbortedOperation("Tie resolution cancelled")

View file

@ -4,7 +4,9 @@ from collections import defaultdict
from warchron.constants import ContextType
from warchron.model.war import War
from warchron.model.campaign import Campaign
from warchron.model.battle import Battle
from warchron.model.exception import DomainError
@dataclass(slots=True)
@ -26,14 +28,17 @@ class ScoreService:
continue
yield from rnd.battles.values()
elif context_type == ContextType.CAMPAIGN:
campaign = war.get_campaign(context_id)
for rnd in campaign.rounds:
if not rnd.is_over:
continue
yield from rnd.battles.values()
campaign: Campaign | None = war.get_campaign(context_id)
if campaign:
for rnd in campaign.rounds:
if not rnd.is_over:
continue
yield from rnd.battles.values()
elif context_type == ContextType.BATTLE:
battle = war.get_battle(context_id)
campaign = war.get_campaign_by_sector(battle.sector_id)
if not campaign:
raise DomainError(f"No campaign found for secor {battle.sector_id}")
rnd = campaign.get_round_by_battle(context_id)
if rnd and rnd.is_over:
yield battle

View file

@ -10,7 +10,11 @@ from warchron.model.war_event import (
InfluenceSpent,
TieResolved,
)
from warchron.model.exception import ForbiddenOperation, RequiresConfirmation
from warchron.model.exception import (
ForbiddenOperation,
RequiresConfirmation,
DomainError,
)
from warchron.model.war_participant import WarParticipant
from warchron.model.objective import Objective
from warchron.model.campaign_participant import CampaignParticipant
@ -80,6 +84,10 @@ class War:
if ev.context_type == ContextType.BATTLE:
battle = self.get_battle(ev.context_id)
campaign = self.get_campaign_by_sector(battle.sector_id)
if not campaign:
raise DomainError(
f"No campaign found for sector {battle.sector_id}"
)
round = campaign.get_round_by_battle(ev.context_id)
round.is_over = False
elif ev.context_type == ContextType.CAMPAIGN:
@ -313,12 +321,12 @@ class War:
return None
# TODO replace multiloops by internal has_* method
def get_campaign_by_sector(self, sector_id: str) -> Campaign:
def get_campaign_by_sector(self, sector_id: str) -> Campaign | None:
for camp in self.campaigns:
for sect in camp.sectors.values():
if sect.id == sector_id:
return camp
raise KeyError(f"Sector {sector_id} not found in any Campaign")
return None
def get_campaign_by_campaign_participant(
self, participant_id: str
@ -396,6 +404,8 @@ class War:
description: str | None,
) -> None:
camp = self.get_campaign_by_sector(sector_id)
if not camp:
raise DomainError(f"No campaign found for sector {sector_id}")
camp.update_sector(
sector_id,
name=name,
@ -409,6 +419,8 @@ class War:
def remove_sector(self, sector_id: str) -> None:
camp = self.get_campaign_by_sector(sector_id)
if not camp:
raise DomainError(f"No campaign found for sector {sector_id}")
camp.remove_sector(sector_id)
# Campaign participant methods

View file

@ -34,7 +34,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(context_name)
grid = self.ui.playersGridLayout
icon_path = (RESOURCES_DIR / Icons._paths[IconName.TOKENS]).as_posix()
token_html = (
@ -71,17 +71,3 @@ class TieDialog(QDialog):
def get_bids(self) -> Dict[str, bool]:
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:
if context_name:
return f"{context_name} tie"
titles = {
ContextType.BATTLE: "Battle tie",
ContextType.CAMPAIGN: "Campaign tie",
ContextType.WAR: "War tie",
ContextType.CHOICE: "Choice tie",
}
return titles.get(context_type, "Tie")