display pairing results in choice table

This commit is contained in:
Maxime Réaux 2026-03-19 09:02:22 +01:00
parent 9e602e8ca4
commit 4396b15c3a
9 changed files with 194 additions and 12 deletions

View file

@ -71,6 +71,9 @@ class IconName(StrEnum):
NP3RDDRAW = auto()
NP3RDBREAK = auto()
NP3RDTIEDRAW = auto()
ALLOCATED = auto()
ALLOCATEDTOKEN = auto()
FALLBACK = auto()
VP_RANK_TO_ICON = {
@ -125,6 +128,8 @@ class Icons:
IconName.NP2ND: "medal-silver.png",
IconName.NP3RD: "medal-bronze.png",
IconName.WARCHRONBACK: "warchron_background.png",
IconName.ALLOCATED: "map.png",
IconName.FALLBACK: "cross-script.png",
}
@classmethod
@ -255,6 +260,11 @@ class Icons:
cls.get_pixmap(IconName.DRAW),
cls.get_pixmap(IconName.TOKEN),
)
elif name == IconName.ALLOCATEDTOKEN:
pix = cls._compose(
cls.get_pixmap(IconName.ALLOCATED),
cls.get_pixmap(IconName.TOKEN),
)
else:
path = RESOURCES_DIR / cls._paths[name]
pix = QPixmap(path.as_posix())
@ -313,3 +323,16 @@ class ContextType(StrEnum):
class ScoreKind(Enum):
VP = auto()
NP = auto()
class ChoiceStatus(StrEnum):
NONE = auto()
TOKEN = auto()
ALLOCATED = auto()
ALLOCATEDTOKEN = auto()
class AllocationType(Enum):
PRIORITY = auto()
SECONDARY = auto()
FALLBACK = auto()

View file

@ -88,6 +88,9 @@ class ChoiceDTO:
priority_sector: str | None
secondary_sector: str | None
comment: str | None
priority_icon: QIcon | None = None
secondary_icon: QIcon | None = None
fallback_icon: QIcon | None = None
@dataclass(frozen=True, slots=True)
@ -103,8 +106,6 @@ class BattleDTO:
state_icon: QIcon | None
player1_icon: QIcon | None
player2_icon: QIcon | None
player1_tooltip: str | None = None
player2_tooltip: str | None = None
@dataclass(frozen=True, slots=True)

View file

@ -4,7 +4,14 @@ from PyQt6.QtWidgets import QDialog
from PyQt6.QtWidgets import QMessageBox
from PyQt6.QtGui import QIcon
from warchron.constants import ItemType, RefreshScope, Icons, IconName, ContextType
from warchron.constants import (
ItemType,
RefreshScope,
Icons,
IconName,
ContextType,
ChoiceStatus,
)
from warchron.model.exception import (
AbortedOperation,
DomainError,
@ -12,6 +19,7 @@ from warchron.model.exception import (
)
from warchron.model.tie_manager import TieResolver, TieContext
from warchron.model.result_checker import ResultChecker
from warchron.model.pairing import Pairing
from warchron.model.round import Round
from warchron.model.war import War
@ -62,6 +70,20 @@ class RoundController:
if choice.secondary_sector_id is not None
else ""
)
priority_icon = None
secondary_icon = None
fallback_icon = None
alloc = Pairing.get_round_allocation(
war,
rnd,
part.id,
)
if alloc.priority != ChoiceStatus.NONE:
priority_icon = QIcon(Icons.get_pixmap(IconName[alloc.priority.name]))
if alloc.secondary != ChoiceStatus.NONE:
secondary_icon = QIcon(Icons.get_pixmap(IconName[alloc.secondary.name]))
if alloc.fallback:
fallback_icon = QIcon(Icons.get_pixmap(IconName.FALLBACK))
choices_for_display.append(
ChoiceDTO(
id=choice.participant_id,
@ -71,9 +93,11 @@ class RoundController:
priority_sector=priority_name,
secondary_sector=secondary_name,
comment=choice.comment,
priority_icon=priority_icon,
secondary_icon=secondary_icon,
fallback_icon=fallback_icon,
)
)
# TODO display allocated sectors and used token
self.app.view.display_round_choices(choices_for_display)
battles_for_display: List[BattleDTO] = []
for sect in sectors:
@ -111,8 +135,7 @@ class RoundController:
winner_name = ""
p1_icon = None
p2_icon = None
p1_tooltip = None
p2_tooltip = None
# TODO use uniform draw/tie icon logic with choice, war, campaign...
if battle.is_draw():
p1_icon = Icons.get(IconName.DRAW)
p2_icon = Icons.get(IconName.DRAW)
@ -131,10 +154,8 @@ class RoundController:
pixmap = Icons.get_pixmap(IconName.TIEBREAK_TOKEN)
if effective_winner == p1_war:
p1_icon = QIcon(pixmap)
p1_tooltip = "Won by tie-break"
else:
p2_icon = QIcon(pixmap)
p2_tooltip = "Won by tie-break"
elif battle.winner_id:
if battle.winner_id == battle.player_1_id:
p1_icon = Icons.get(IconName.WIN)
@ -153,8 +174,6 @@ class RoundController:
state_icon=state_icon,
player1_icon=p1_icon,
player2_icon=p2_icon,
player1_tooltip=p1_tooltip,
player2_tooltip=p2_tooltip,
)
)
self.app.view.display_round_battles(battles_for_display)

View file

@ -1,10 +1,11 @@
from __future__ import annotations
from typing import Dict, List, Callable, Tuple
from dataclasses import dataclass
from uuid import uuid4
import random
from warchron.constants import ContextType, ScoreKind
from warchron.constants import ContextType, ScoreKind, ChoiceStatus, AllocationType
from warchron.model.exception import (
DomainError,
ForbiddenOperation,
@ -15,7 +16,7 @@ from warchron.model.round import Round
from warchron.model.battle import Battle
from warchron.model.score_service import ScoreService
from warchron.model.tie_manager import TieResolver, TieContext
from warchron.model.war_event import TieResolved
from warchron.model.war_event import TieResolved, InfluenceSpent
from warchron.model.score_service import ParticipantScore
ResolveTiesCallback = Callable[
@ -24,6 +25,13 @@ ResolveTiesCallback = Callable[
]
@dataclass(frozen=True, slots=True)
class AllocationResult:
priority: ChoiceStatus
secondary: ChoiceStatus
fallback: bool
class Pairing:
@staticmethod
@ -218,6 +226,10 @@ class Pairing:
)
or len(active) <= places
):
print(
f"Natural or acceptable draw for sector {sector_id} with participants:\n",
context.participants,
)
war.events.append(
TieResolved(
None,
@ -234,6 +246,10 @@ class Pairing:
bids = bids_map[current_context.key()]
# confirmed draw if current bids are 0
if bids is not None and not any(bids.values()):
print(
f"Confirmed draw for sector {sector_id} with participants:\n",
context.participants,
)
war.events.append(
TieResolved(
None,
@ -278,3 +294,100 @@ class Pairing:
remaining.remove(pid)
continue
raise DomainError(f"Ambiguous fallback for participant {pid}")
@staticmethod
def get_allocation_kind(
war: War,
round_id: str,
participant_id: str,
sector_id: str,
) -> AllocationType:
round = war.get_round(round_id)
choice = round.choices.get(participant_id)
if not choice:
raise DomainError(f"No choice found for participant {participant_id}")
if choice.priority_sector_id == sector_id:
return AllocationType.PRIORITY
if choice.secondary_sector_id == sector_id:
return AllocationType.SECONDARY
return AllocationType.FALLBACK
@staticmethod
def participant_spent_token(
war: War,
round_id: str,
sector_id: str | None,
war_participant_id: str,
) -> bool:
if sector_id is None:
return False
for ev in war.events:
if not isinstance(ev, InfluenceSpent):
continue
if ev.context_type != ContextType.CHOICE:
continue
if ev.context_id != round_id:
continue
if ev.sector_id != sector_id:
continue
if ev.participant_id == war_participant_id:
return True
return False
@staticmethod
def get_round_allocation(
war: War,
round: Round,
campaign_participant_id: str,
) -> AllocationResult:
choice = round.choices[campaign_participant_id]
campaign = war.get_campaign_by_round(round.id)
if campaign is None:
raise DomainError(f"No campaign found for round {round.id}")
war_pid = campaign.campaign_to_war_part_id(campaign_participant_id)
token_priority = Pairing.participant_spent_token(
war,
round.id,
choice.priority_sector_id,
war_pid,
)
token_secondary = Pairing.participant_spent_token(
war,
round.id,
choice.secondary_sector_id,
war_pid,
)
battle = round.get_battle_for_participant(campaign_participant_id)
allocation = AllocationType.FALLBACK
if battle:
allocation = Pairing.get_allocation_kind(
war,
round.id,
campaign_participant_id,
battle.sector_id,
)
priority_status = ChoiceStatus.NONE
secondary_status = ChoiceStatus.NONE
fallback = allocation == AllocationType.FALLBACK
if allocation == AllocationType.PRIORITY:
priority_status = (
ChoiceStatus.ALLOCATEDTOKEN
if token_priority
else ChoiceStatus.ALLOCATED
)
elif token_priority:
priority_status = ChoiceStatus.TOKEN
if allocation == AllocationType.SECONDARY:
secondary_status = (
ChoiceStatus.ALLOCATEDTOKEN
if token_secondary
else ChoiceStatus.ALLOCATED
)
elif token_secondary:
secondary_status = ChoiceStatus.TOKEN
return AllocationResult(
priority=priority_status,
secondary=secondary_status,
fallback=fallback,
)

View file

@ -132,6 +132,18 @@ class Round:
def get_battle(self, sector_id: str) -> Battle | None:
return self.battles.get(sector_id)
def get_battle_for_participant(
self,
campaign_participant_id: str,
) -> Battle | None:
for battle in self.battles.values():
if (
battle.player_1_id == campaign_participant_id
or battle.player_2_id == campaign_participant_id
):
return battle
return None
def has_battle_with_sector(self, sector_id: str) -> bool:
return any(bat.sector_id == sector_id for bat in self.battles.values())

View file

@ -327,6 +327,7 @@ class TieResolver:
context_id=context.context_id,
tie_id=tie_id,
objective_id=context.objective_id,
sector_id=context.sector_id,
)
)
@ -418,6 +419,7 @@ class TieResolver:
tie_id=tie_id,
score_value=context.score_value,
objective_id=context.objective_id,
sector_id=context.sector_id,
)
)
return
@ -432,6 +434,7 @@ class TieResolver:
tie_id=tie_id,
score_value=context.score_value,
objective_id=context.objective_id,
sector_id=context.sector_id,
)
)
return

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -603,15 +603,26 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
table = self.choicesTable
table.setSortingEnabled(False)
table.clearContents()
table.setColumnCount(4)
table.setHorizontalHeaderLabels(["Participant", "Priority", "Secondary", ""])
table.setRowCount(len(participants))
table.setIconSize(QSize(32, 16))
for row, choice in enumerate(participants):
participant_item = QtWidgets.QTableWidgetItem(choice.participant_name)
priority_item = QtWidgets.QTableWidgetItem(choice.priority_sector)
if choice.priority_icon:
priority_item.setIcon(choice.priority_icon)
secondary_item = QtWidgets.QTableWidgetItem(choice.secondary_sector)
if choice.secondary_icon:
secondary_item.setIcon(choice.secondary_icon)
status_item = QtWidgets.QTableWidgetItem()
if choice.fallback_icon:
status_item.setIcon(choice.fallback_icon)
participant_item.setData(Qt.ItemDataRole.UserRole, choice.id)
table.setItem(row, 0, participant_item)
table.setItem(row, 1, priority_item)
table.setItem(row, 2, secondary_item)
table.setItem(row, 3, status_item)
table.setSortingEnabled(True)
table.resizeColumnsToContents()