display pairing results in choice table
This commit is contained in:
parent
9e602e8ca4
commit
4396b15c3a
9 changed files with 194 additions and 12 deletions
|
|
@ -71,6 +71,9 @@ class IconName(StrEnum):
|
||||||
NP3RDDRAW = auto()
|
NP3RDDRAW = auto()
|
||||||
NP3RDBREAK = auto()
|
NP3RDBREAK = auto()
|
||||||
NP3RDTIEDRAW = auto()
|
NP3RDTIEDRAW = auto()
|
||||||
|
ALLOCATED = auto()
|
||||||
|
ALLOCATEDTOKEN = auto()
|
||||||
|
FALLBACK = auto()
|
||||||
|
|
||||||
|
|
||||||
VP_RANK_TO_ICON = {
|
VP_RANK_TO_ICON = {
|
||||||
|
|
@ -125,6 +128,8 @@ class Icons:
|
||||||
IconName.NP2ND: "medal-silver.png",
|
IconName.NP2ND: "medal-silver.png",
|
||||||
IconName.NP3RD: "medal-bronze.png",
|
IconName.NP3RD: "medal-bronze.png",
|
||||||
IconName.WARCHRONBACK: "warchron_background.png",
|
IconName.WARCHRONBACK: "warchron_background.png",
|
||||||
|
IconName.ALLOCATED: "map.png",
|
||||||
|
IconName.FALLBACK: "cross-script.png",
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -255,6 +260,11 @@ class Icons:
|
||||||
cls.get_pixmap(IconName.DRAW),
|
cls.get_pixmap(IconName.DRAW),
|
||||||
cls.get_pixmap(IconName.TOKEN),
|
cls.get_pixmap(IconName.TOKEN),
|
||||||
)
|
)
|
||||||
|
elif name == IconName.ALLOCATEDTOKEN:
|
||||||
|
pix = cls._compose(
|
||||||
|
cls.get_pixmap(IconName.ALLOCATED),
|
||||||
|
cls.get_pixmap(IconName.TOKEN),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
path = RESOURCES_DIR / cls._paths[name]
|
path = RESOURCES_DIR / cls._paths[name]
|
||||||
pix = QPixmap(path.as_posix())
|
pix = QPixmap(path.as_posix())
|
||||||
|
|
@ -313,3 +323,16 @@ class ContextType(StrEnum):
|
||||||
class ScoreKind(Enum):
|
class ScoreKind(Enum):
|
||||||
VP = auto()
|
VP = auto()
|
||||||
NP = auto()
|
NP = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class ChoiceStatus(StrEnum):
|
||||||
|
NONE = auto()
|
||||||
|
TOKEN = auto()
|
||||||
|
ALLOCATED = auto()
|
||||||
|
ALLOCATEDTOKEN = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class AllocationType(Enum):
|
||||||
|
PRIORITY = auto()
|
||||||
|
SECONDARY = auto()
|
||||||
|
FALLBACK = auto()
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,9 @@ class ChoiceDTO:
|
||||||
priority_sector: str | None
|
priority_sector: str | None
|
||||||
secondary_sector: str | None
|
secondary_sector: str | None
|
||||||
comment: 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)
|
@dataclass(frozen=True, slots=True)
|
||||||
|
|
@ -103,8 +106,6 @@ class BattleDTO:
|
||||||
state_icon: QIcon | None
|
state_icon: QIcon | None
|
||||||
player1_icon: QIcon | None
|
player1_icon: QIcon | None
|
||||||
player2_icon: QIcon | None
|
player2_icon: QIcon | None
|
||||||
player1_tooltip: str | None = None
|
|
||||||
player2_tooltip: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,14 @@ from PyQt6.QtWidgets import QDialog
|
||||||
from PyQt6.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
from PyQt6.QtGui import QIcon
|
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 (
|
from warchron.model.exception import (
|
||||||
AbortedOperation,
|
AbortedOperation,
|
||||||
DomainError,
|
DomainError,
|
||||||
|
|
@ -12,6 +19,7 @@ from warchron.model.exception import (
|
||||||
)
|
)
|
||||||
from warchron.model.tie_manager import TieResolver, TieContext
|
from warchron.model.tie_manager import TieResolver, TieContext
|
||||||
from warchron.model.result_checker import ResultChecker
|
from warchron.model.result_checker import ResultChecker
|
||||||
|
from warchron.model.pairing import Pairing
|
||||||
from warchron.model.round import Round
|
from warchron.model.round import Round
|
||||||
from warchron.model.war import War
|
from warchron.model.war import War
|
||||||
|
|
||||||
|
|
@ -62,6 +70,20 @@ class RoundController:
|
||||||
if choice.secondary_sector_id is not None
|
if choice.secondary_sector_id is not None
|
||||||
else ""
|
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(
|
choices_for_display.append(
|
||||||
ChoiceDTO(
|
ChoiceDTO(
|
||||||
id=choice.participant_id,
|
id=choice.participant_id,
|
||||||
|
|
@ -71,9 +93,11 @@ class RoundController:
|
||||||
priority_sector=priority_name,
|
priority_sector=priority_name,
|
||||||
secondary_sector=secondary_name,
|
secondary_sector=secondary_name,
|
||||||
comment=choice.comment,
|
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)
|
self.app.view.display_round_choices(choices_for_display)
|
||||||
battles_for_display: List[BattleDTO] = []
|
battles_for_display: List[BattleDTO] = []
|
||||||
for sect in sectors:
|
for sect in sectors:
|
||||||
|
|
@ -111,8 +135,7 @@ class RoundController:
|
||||||
winner_name = ""
|
winner_name = ""
|
||||||
p1_icon = None
|
p1_icon = None
|
||||||
p2_icon = None
|
p2_icon = None
|
||||||
p1_tooltip = None
|
# TODO use uniform draw/tie icon logic with choice, war, campaign...
|
||||||
p2_tooltip = None
|
|
||||||
if battle.is_draw():
|
if battle.is_draw():
|
||||||
p1_icon = Icons.get(IconName.DRAW)
|
p1_icon = Icons.get(IconName.DRAW)
|
||||||
p2_icon = Icons.get(IconName.DRAW)
|
p2_icon = Icons.get(IconName.DRAW)
|
||||||
|
|
@ -131,10 +154,8 @@ class RoundController:
|
||||||
pixmap = Icons.get_pixmap(IconName.TIEBREAK_TOKEN)
|
pixmap = Icons.get_pixmap(IconName.TIEBREAK_TOKEN)
|
||||||
if effective_winner == p1_war:
|
if effective_winner == p1_war:
|
||||||
p1_icon = QIcon(pixmap)
|
p1_icon = QIcon(pixmap)
|
||||||
p1_tooltip = "Won by tie-break"
|
|
||||||
else:
|
else:
|
||||||
p2_icon = QIcon(pixmap)
|
p2_icon = QIcon(pixmap)
|
||||||
p2_tooltip = "Won by tie-break"
|
|
||||||
elif battle.winner_id:
|
elif battle.winner_id:
|
||||||
if battle.winner_id == battle.player_1_id:
|
if battle.winner_id == battle.player_1_id:
|
||||||
p1_icon = Icons.get(IconName.WIN)
|
p1_icon = Icons.get(IconName.WIN)
|
||||||
|
|
@ -153,8 +174,6 @@ class RoundController:
|
||||||
state_icon=state_icon,
|
state_icon=state_icon,
|
||||||
player1_icon=p1_icon,
|
player1_icon=p1_icon,
|
||||||
player2_icon=p2_icon,
|
player2_icon=p2_icon,
|
||||||
player1_tooltip=p1_tooltip,
|
|
||||||
player2_tooltip=p2_tooltip,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.app.view.display_round_battles(battles_for_display)
|
self.app.view.display_round_battles(battles_for_display)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import Dict, List, Callable, Tuple
|
from typing import Dict, List, Callable, Tuple
|
||||||
|
from dataclasses import dataclass
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from warchron.constants import ContextType, ScoreKind
|
from warchron.constants import ContextType, ScoreKind, ChoiceStatus, AllocationType
|
||||||
from warchron.model.exception import (
|
from warchron.model.exception import (
|
||||||
DomainError,
|
DomainError,
|
||||||
ForbiddenOperation,
|
ForbiddenOperation,
|
||||||
|
|
@ -15,7 +16,7 @@ from warchron.model.round import Round
|
||||||
from warchron.model.battle import Battle
|
from warchron.model.battle import Battle
|
||||||
from warchron.model.score_service import ScoreService
|
from warchron.model.score_service import ScoreService
|
||||||
from warchron.model.tie_manager import TieResolver, TieContext
|
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
|
from warchron.model.score_service import ParticipantScore
|
||||||
|
|
||||||
ResolveTiesCallback = Callable[
|
ResolveTiesCallback = Callable[
|
||||||
|
|
@ -24,6 +25,13 @@ ResolveTiesCallback = Callable[
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class AllocationResult:
|
||||||
|
priority: ChoiceStatus
|
||||||
|
secondary: ChoiceStatus
|
||||||
|
fallback: bool
|
||||||
|
|
||||||
|
|
||||||
class Pairing:
|
class Pairing:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -218,6 +226,10 @@ class Pairing:
|
||||||
)
|
)
|
||||||
or len(active) <= places
|
or len(active) <= places
|
||||||
):
|
):
|
||||||
|
print(
|
||||||
|
f"Natural or acceptable draw for sector {sector_id} with participants:\n",
|
||||||
|
context.participants,
|
||||||
|
)
|
||||||
war.events.append(
|
war.events.append(
|
||||||
TieResolved(
|
TieResolved(
|
||||||
None,
|
None,
|
||||||
|
|
@ -234,6 +246,10 @@ class Pairing:
|
||||||
bids = bids_map[current_context.key()]
|
bids = bids_map[current_context.key()]
|
||||||
# confirmed draw if current bids are 0
|
# confirmed draw if current bids are 0
|
||||||
if bids is not None and not any(bids.values()):
|
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(
|
war.events.append(
|
||||||
TieResolved(
|
TieResolved(
|
||||||
None,
|
None,
|
||||||
|
|
@ -278,3 +294,100 @@ class Pairing:
|
||||||
remaining.remove(pid)
|
remaining.remove(pid)
|
||||||
continue
|
continue
|
||||||
raise DomainError(f"Ambiguous fallback for participant {pid}")
|
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,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,18 @@ class Round:
|
||||||
def get_battle(self, sector_id: str) -> Battle | None:
|
def get_battle(self, sector_id: str) -> Battle | None:
|
||||||
return self.battles.get(sector_id)
|
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:
|
def has_battle_with_sector(self, sector_id: str) -> bool:
|
||||||
return any(bat.sector_id == sector_id for bat in self.battles.values())
|
return any(bat.sector_id == sector_id for bat in self.battles.values())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -327,6 +327,7 @@ class TieResolver:
|
||||||
context_id=context.context_id,
|
context_id=context.context_id,
|
||||||
tie_id=tie_id,
|
tie_id=tie_id,
|
||||||
objective_id=context.objective_id,
|
objective_id=context.objective_id,
|
||||||
|
sector_id=context.sector_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -418,6 +419,7 @@ class TieResolver:
|
||||||
tie_id=tie_id,
|
tie_id=tie_id,
|
||||||
score_value=context.score_value,
|
score_value=context.score_value,
|
||||||
objective_id=context.objective_id,
|
objective_id=context.objective_id,
|
||||||
|
sector_id=context.sector_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
@ -432,6 +434,7 @@ class TieResolver:
|
||||||
tie_id=tie_id,
|
tie_id=tie_id,
|
||||||
score_value=context.score_value,
|
score_value=context.score_value,
|
||||||
objective_id=context.objective_id,
|
objective_id=context.objective_id,
|
||||||
|
sector_id=context.sector_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
BIN
src/warchron/view/resources/cross-script.png
Normal file
BIN
src/warchron/view/resources/cross-script.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 663 B |
BIN
src/warchron/view/resources/map.png
Normal file
BIN
src/warchron/view/resources/map.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -603,15 +603,26 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||||
table = self.choicesTable
|
table = self.choicesTable
|
||||||
table.setSortingEnabled(False)
|
table.setSortingEnabled(False)
|
||||||
table.clearContents()
|
table.clearContents()
|
||||||
|
table.setColumnCount(4)
|
||||||
|
table.setHorizontalHeaderLabels(["Participant", "Priority", "Secondary", ""])
|
||||||
table.setRowCount(len(participants))
|
table.setRowCount(len(participants))
|
||||||
|
table.setIconSize(QSize(32, 16))
|
||||||
for row, choice in enumerate(participants):
|
for row, choice in enumerate(participants):
|
||||||
participant_item = QtWidgets.QTableWidgetItem(choice.participant_name)
|
participant_item = QtWidgets.QTableWidgetItem(choice.participant_name)
|
||||||
priority_item = QtWidgets.QTableWidgetItem(choice.priority_sector)
|
priority_item = QtWidgets.QTableWidgetItem(choice.priority_sector)
|
||||||
|
if choice.priority_icon:
|
||||||
|
priority_item.setIcon(choice.priority_icon)
|
||||||
secondary_item = QtWidgets.QTableWidgetItem(choice.secondary_sector)
|
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)
|
participant_item.setData(Qt.ItemDataRole.UserRole, choice.id)
|
||||||
table.setItem(row, 0, participant_item)
|
table.setItem(row, 0, participant_item)
|
||||||
table.setItem(row, 1, priority_item)
|
table.setItem(row, 1, priority_item)
|
||||||
table.setItem(row, 2, secondary_item)
|
table.setItem(row, 2, secondary_item)
|
||||||
|
table.setItem(row, 3, status_item)
|
||||||
table.setSortingEnabled(True)
|
table.setSortingEnabled(True)
|
||||||
table.resizeColumnsToContents()
|
table.resizeColumnsToContents()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue