fix icon mapping in campaign ranking

This commit is contained in:
Maxime Réaux 2026-02-23 19:28:13 +01:00
parent 0bfe27e0d3
commit d766befd31
5 changed files with 113 additions and 58 deletions

View file

@ -50,13 +50,25 @@ class IconName(str, Enum):
NP3RD = "np3rd" NP3RD = "np3rd"
TIEBREAK_TOKEN = auto() TIEBREAK_TOKEN = auto()
VP1STDRAW = auto() VP1STDRAW = auto()
VP1STTIEBREAK = auto() VP1STBREAK = auto()
VP1STTIEDRAW = auto()
VP2NDDRAW = auto() VP2NDDRAW = auto()
VP2NDTIEBREAK = auto() VP2NDBREAK = auto()
VP2NDTIEDRAW = auto()
VP3RDDRAW = auto() VP3RDDRAW = auto()
VP3RDTIEBREAK = auto() VP3RDBREAK = auto()
VP3RDTIEDRAW = auto()
VPNTHDRAW = auto() VPNTHDRAW = auto()
VPNTHTIEBREAK = auto() VPNTHBREAK = auto()
VPNTHTIEDRAW = auto()
RANK_TO_ICON = {
1: IconName.VP1ST,
2: IconName.VP2ND,
3: IconName.VP3RD,
4: IconName.VPNTH,
}
class Icons: class Icons:
@ -119,41 +131,65 @@ class Icons:
cls.get_pixmap(IconName.VP1ST), cls.get_pixmap(IconName.VP1ST),
cls.get_pixmap(IconName.DRAW), cls.get_pixmap(IconName.DRAW),
) )
elif name == IconName.VP1STTIEBREAK: elif name == IconName.VP1STBREAK:
pix = cls._compose( pix = cls._compose(
cls.get_pixmap(IconName.VP1ST), cls.get_pixmap(IconName.VP1ST),
cls.get_pixmap(IconName.TOKEN), cls.get_pixmap(IconName.TOKEN),
) )
elif name == IconName.VP1STTIEDRAW:
pix = cls._compose(
cls.get_pixmap(IconName.VP1ST),
cls.get_pixmap(IconName.DRAW),
cls.get_pixmap(IconName.TOKEN),
)
elif name == IconName.VP2NDDRAW: elif name == IconName.VP2NDDRAW:
pix = cls._compose( pix = cls._compose(
cls.get_pixmap(IconName.VP2ND), cls.get_pixmap(IconName.VP2ND),
cls.get_pixmap(IconName.DRAW), cls.get_pixmap(IconName.DRAW),
) )
elif name == IconName.VP2NDTIEBREAK: elif name == IconName.VP2NDBREAK:
pix = cls._compose( pix = cls._compose(
cls.get_pixmap(IconName.VP2ND), cls.get_pixmap(IconName.VP2ND),
cls.get_pixmap(IconName.TOKEN), cls.get_pixmap(IconName.TOKEN),
) )
elif name == IconName.VP2NDTIEDRAW:
pix = cls._compose(
cls.get_pixmap(IconName.VP2ND),
cls.get_pixmap(IconName.DRAW),
cls.get_pixmap(IconName.TOKEN),
)
elif name == IconName.VP3RDDRAW: elif name == IconName.VP3RDDRAW:
pix = cls._compose( pix = cls._compose(
cls.get_pixmap(IconName.VP3RD), cls.get_pixmap(IconName.VP3RD),
cls.get_pixmap(IconName.DRAW), cls.get_pixmap(IconName.DRAW),
) )
elif name == IconName.VP3RDTIEBREAK: elif name == IconName.VP3RDBREAK:
pix = cls._compose( pix = cls._compose(
cls.get_pixmap(IconName.VP3RD), cls.get_pixmap(IconName.VP3RD),
cls.get_pixmap(IconName.TOKEN), cls.get_pixmap(IconName.TOKEN),
) )
elif name == IconName.VP3RDTIEDRAW:
pix = cls._compose(
cls.get_pixmap(IconName.VP3RD),
cls.get_pixmap(IconName.DRAW),
cls.get_pixmap(IconName.TOKEN),
)
elif name == IconName.VPNTHDRAW: elif name == IconName.VPNTHDRAW:
pix = cls._compose( pix = cls._compose(
cls.get_pixmap(IconName.VPNTH), cls.get_pixmap(IconName.VPNTH),
cls.get_pixmap(IconName.DRAW), cls.get_pixmap(IconName.DRAW),
) )
elif name == IconName.VPNTHTIEBREAK: elif name == IconName.VPNTHBREAK:
pix = cls._compose( pix = cls._compose(
cls.get_pixmap(IconName.VPNTH), cls.get_pixmap(IconName.VPNTH),
cls.get_pixmap(IconName.TOKEN), cls.get_pixmap(IconName.TOKEN),
) )
elif name == IconName.VPNTHTIEDRAW:
pix = cls._compose(
cls.get_pixmap(IconName.VPNTH),
cls.get_pixmap(IconName.DRAW),
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())
@ -161,14 +197,20 @@ class Icons:
return pix return pix
@staticmethod @staticmethod
def _compose(left: QPixmap, right: QPixmap) -> QPixmap: def _compose(*pixmaps: QPixmap) -> QPixmap:
w = left.width() + right.width() if not pixmaps:
h = max(left.height(), right.height()) return QPixmap()
if len(pixmaps) == 1:
return pixmaps[0]
w = sum(p.width() for p in pixmaps)
h = max(p.height() for p in pixmaps)
result = QPixmap(w, h) result = QPixmap(w, h)
result.fill(Qt.GlobalColor.transparent) result.fill(Qt.GlobalColor.transparent)
painter = QPainter(result) painter = QPainter(result)
painter.drawPixmap(0, (h - left.height()) // 2, left) x = 0
painter.drawPixmap(left.width(), (h - right.height()) // 2, right) for p in pixmaps:
painter.drawPixmap(x, (h - p.height()) // 2, p)
x += p.width()
painter.end() painter.end()
return result return result

View file

@ -3,7 +3,14 @@ from typing import List, Dict, TYPE_CHECKING
from PyQt6.QtWidgets import QMessageBox, QDialog from PyQt6.QtWidgets import QMessageBox, QDialog
from PyQt6.QtGui import QIcon from PyQt6.QtGui import QIcon
from warchron.constants import RefreshScope, ContextType, ItemType, Icons, IconName from warchron.constants import (
RefreshScope,
ContextType,
ItemType,
Icons,
IconName,
RANK_TO_ICON,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from warchron.controller.app_controller import AppController from warchron.controller.app_controller import AppController
@ -45,40 +52,25 @@ class CampaignController:
war, ContextType.CAMPAIGN, campaign.id, scores war, ContextType.CAMPAIGN, campaign.id, scores
) )
icon_map = {} icon_map = {}
for rank, group in ranking: for rank, group, token_map in ranking:
vp = scores[group[0]].victory_points base_icon = RANK_TO_ICON.get(rank, IconName.VPNTH)
tie_id = f"{campaign.id}:score:{vp}" tie_id = f"{campaign.id}:score:{scores[group[0]].victory_points}"
is_tie = len(group) > 1 tie_resolved = TieResolver.is_tie_resolved(
broken = TieResolver.was_tie_broken_by_tokens( war, ContextType.CAMPAIGN, tie_id
war,
ContextType.CAMPAIGN,
tie_id,
) )
# choose icon name
if rank == 1:
base = IconName.VP1ST
draw = IconName.VP1STDRAW
tb = IconName.VP1STTIEBREAK
elif rank == 2:
base = IconName.VP2ND
draw = IconName.VP2NDDRAW
tb = IconName.VP2NDTIEBREAK
elif rank == 3:
base = IconName.VP3RD
draw = IconName.VP3RDDRAW
tb = IconName.VP3RDTIEBREAK
else:
base = IconName.VPNTH
draw = IconName.VPNTHDRAW
tb = IconName.VPNTHTIEBREAK
if not is_tie:
icon = Icons.get(base)
elif not broken:
icon = QIcon(Icons.get_pixmap(draw))
else:
icon = QIcon(Icons.get_pixmap(tb))
for pid in group: for pid in group:
icon_map[pid] = icon spent = token_map.get(pid, 0)
if not tie_resolved and spent == 0:
icon_name = getattr(IconName, f"{base_icon.name}DRAW")
elif tie_resolved and spent == 0 and len(group) > 1:
icon_name = getattr(IconName, f"{base_icon.name}DRAW")
elif tie_resolved and spent > 0 and len(group) == 1:
icon_name = getattr(IconName, f"{base_icon.name}BREAK")
elif tie_resolved and spent > 0 and len(group) > 1:
icon_name = getattr(IconName, f"{base_icon.name}TIEDRAW")
else:
icon_name = base_icon
icon_map[pid] = QIcon(Icons.get_pixmap(icon_name))
return icon_map return icon_map
def _fill_campaign_details(self, campaign_id: str) -> None: def _fill_campaign_details(self, campaign_id: str) -> None:

View file

@ -37,24 +37,23 @@ class ResultChecker:
context_type: ContextType, context_type: ContextType,
context_id: str, context_id: str,
scores: Dict[str, ParticipantScore], scores: Dict[str, ParticipantScore],
) -> List[Tuple[int, List[str]]]: ) -> List[Tuple[int, List[str], Dict[str, int]]]:
vp_buckets: Dict[int, List[str]] = defaultdict(list) vp_buckets: Dict[int, List[str]] = defaultdict(list)
for pid, score in scores.items(): for pid, score in scores.items():
vp_buckets[score.victory_points].append(pid) vp_buckets[score.victory_points].append(pid)
sorted_vps = sorted(vp_buckets.keys(), reverse=True) sorted_vps = sorted(vp_buckets.keys(), reverse=True)
ranking: List[Tuple[int, List[str]]] = [] ranking: List[Tuple[int, List[str], Dict[str, int]]] = []
current_rank = 1 current_rank = 1
for vp in sorted_vps: for vp in sorted_vps:
participants = vp_buckets[vp] participants = vp_buckets[vp]
# no tie
if len(participants) == 1:
ranking.append((current_rank, participants))
current_rank += 1
continue
tie_id = f"{context_id}:score:{vp}" tie_id = f"{context_id}:score:{vp}"
# tie unresolved → shared rank (theoretically impossible) # no tie
if not TieResolver.is_tie_resolved(war, context_type, tie_id): if len(participants) == 1 or not TieResolver.is_tie_resolved(
ranking.append((current_rank, participants)) war, context_type, tie_id
):
ranking.append(
(current_rank, participants, {pid: 0 for pid in participants})
)
current_rank += 1 current_rank += 1
continue continue
# apply token ranking # apply token ranking
@ -64,7 +63,11 @@ class ResultChecker:
tie_id, tie_id,
participants, participants,
) )
tokens_spent = TieResolver.tokens_spent_map(
war, context_type, tie_id, participants
)
for group in groups: for group in groups:
ranking.append((current_rank, group)) group_tokens = {pid: tokens_spent[pid] for pid in group}
ranking.append((current_rank, group, group_tokens))
current_rank += 1 current_rank += 1
return ranking return ranking

View file

@ -131,6 +131,24 @@ class TieResolver:
groups[-1].append(pid) groups[-1].append(pid)
return groups return groups
@staticmethod
def tokens_spent_map(
war: War,
context_type: ContextType,
context_id: str,
participants: List[str],
) -> Dict[str, int]:
spent = {pid: 0 for pid in participants}
for ev in war.events:
if (
isinstance(ev, InfluenceSpent)
and ev.context_type == context_type
and ev.context_id == context_id
and ev.participant_id in spent
):
spent[ev.participant_id] += ev.amount
return spent
@staticmethod @staticmethod
def get_active_participants( def get_active_participants(
war: War, war: War,

View file

@ -475,7 +475,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
table.setColumnCount(len(headers)) table.setColumnCount(len(headers))
table.setHorizontalHeaderLabels(headers) table.setHorizontalHeaderLabels(headers)
table.setRowCount(len(participants)) table.setRowCount(len(participants))
table.setIconSize(QSize(32, 16)) table.setIconSize(QSize(48, 16))
for row, part in enumerate(participants): for row, part in enumerate(participants):
name_item = QtWidgets.QTableWidgetItem(part.player_name) name_item = QtWidgets.QTableWidgetItem(part.player_name)
if part.rank_icon: if part.rank_icon: