display tiebreak place

This commit is contained in:
Maxime Réaux 2026-03-24 16:26:52 +01:00
parent c144845376
commit a3144dc3c9
5 changed files with 73 additions and 19 deletions

View file

@ -135,11 +135,6 @@ class WarParticipantScoreDTO:
objective_icons: Dict[str, QIcon] = field(default_factory=dict)
@dataclass
class TieDialogData:
title: str
@dataclass
class WarSettingsDTO:
major_value: int

View file

@ -1,4 +1,5 @@
from typing import Dict
from dataclasses import dataclass
from PyQt6.QtGui import QIcon
@ -11,16 +12,20 @@ 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
from warchron.model.scoring import ParticipantScore, ScoreComputer
from warchron.model.checking import ResultChecker
from warchron.model.exception import DomainError
@dataclass
class TieDialogData:
title: str
class Presenter:
@staticmethod
@ -30,17 +35,16 @@ class Presenter:
campaign: Campaign | None = None,
round: Round | None = None,
) -> TieDialogData:
# TODO display Nth place
if ctx.context_type == ContextType.WAR:
if ctx.objective_id:
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:
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")
return TieDialogData(
f"{level} objective tie for {rank_label}{obj.name}"
)
return TieDialogData(f"{level} tie for {rank_label}")
if ctx.context_type == ContextType.BATTLE:
if campaign:
sector = campaign.sectors[ctx.context_id]
@ -177,3 +181,57 @@ 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

View file

@ -334,6 +334,7 @@ class Pairing:
raise DomainError("Campaign not found")
match_counts = Pairing.build_match_count(campaign)
available_battles = round.get_battles_with_places()
# FIXME no error when all participants allocated
if not available_battles:
raise DomainError("No available battle remaining")
occupancy = {

View file

@ -67,7 +67,7 @@ class Ui_tieDialog(object):
def retranslateUi(self, tieDialog):
_translate = QtCore.QCoreApplication.translate
tieDialog.setWindowTitle(_translate("tieDialog", "Tie"))
tieDialog.setWindowTitle(_translate("tieDialog", "Tie-break"))
self.tieContext.setText(_translate("tieDialog", "Battle tie"))

View file

@ -14,7 +14,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Tie</string>
<string>Tie-break</string>
</property>
<property name="windowIcon">
<iconset>