Compare commits

..

No commits in common. "d766befd312b82783ce4f0b5ff700ef3920c21c4" and "60992c22df206ea2d4c5bff5c6787fca7b69125a" have entirely different histories.

14 changed files with 13 additions and 236 deletions

View file

@ -41,34 +41,7 @@ class IconName(str, Enum):
WARCHRON = "warchron"
TOKEN = "token"
TOKENS = "tokens"
VP1ST = "vp1st"
VP2ND = "vp2nd"
VP3RD = "vp3rd"
VPNTH = "vpnth"
NP1ST = "np1st"
NP2ND = "np2nd"
NP3RD = "np3rd"
TIEBREAK_TOKEN = auto()
VP1STDRAW = auto()
VP1STBREAK = auto()
VP1STTIEDRAW = auto()
VP2NDDRAW = auto()
VP2NDBREAK = auto()
VP2NDTIEDRAW = auto()
VP3RDDRAW = auto()
VP3RDBREAK = auto()
VP3RDTIEDRAW = auto()
VPNTHDRAW = auto()
VPNTHBREAK = auto()
VPNTHTIEDRAW = auto()
RANK_TO_ICON = {
1: IconName.VP1ST,
2: IconName.VP2ND,
3: IconName.VP3RD,
4: IconName.VPNTH,
}
class Icons:
@ -101,13 +74,6 @@ class Icons:
IconName.WARCHRON: "warchron_logo_background.png",
IconName.TOKEN: "point.png",
IconName.TOKENS: "points.png",
IconName.VP1ST: "trophy.png",
IconName.VP2ND: "trophy-silver.png",
IconName.VP3RD: "trophy-bronze.png",
IconName.VPNTH: "ribbon.png",
IconName.NP1ST: "medal.png",
IconName.NP2ND: "medal-silver.png",
IconName.NP3RD: "medal-bronze.png",
}
@classmethod
@ -126,70 +92,6 @@ class Icons:
cls.get_pixmap(IconName.TIEBREAK),
cls.get_pixmap(IconName.TOKEN),
)
elif name == IconName.VP1STDRAW:
pix = cls._compose(
cls.get_pixmap(IconName.VP1ST),
cls.get_pixmap(IconName.DRAW),
)
elif name == IconName.VP1STBREAK:
pix = cls._compose(
cls.get_pixmap(IconName.VP1ST),
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:
pix = cls._compose(
cls.get_pixmap(IconName.VP2ND),
cls.get_pixmap(IconName.DRAW),
)
elif name == IconName.VP2NDBREAK:
pix = cls._compose(
cls.get_pixmap(IconName.VP2ND),
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:
pix = cls._compose(
cls.get_pixmap(IconName.VP3RD),
cls.get_pixmap(IconName.DRAW),
)
elif name == IconName.VP3RDBREAK:
pix = cls._compose(
cls.get_pixmap(IconName.VP3RD),
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:
pix = cls._compose(
cls.get_pixmap(IconName.VPNTH),
cls.get_pixmap(IconName.DRAW),
)
elif name == IconName.VPNTHBREAK:
pix = cls._compose(
cls.get_pixmap(IconName.VPNTH),
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:
path = RESOURCES_DIR / cls._paths[name]
pix = QPixmap(path.as_posix())
@ -197,20 +99,14 @@ class Icons:
return pix
@staticmethod
def _compose(*pixmaps: QPixmap) -> QPixmap:
if not pixmaps:
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)
def _compose(left: QPixmap, right: QPixmap) -> QPixmap:
w = left.width() + right.width()
h = max(left.height(), right.height())
result = QPixmap(w, h)
result.fill(Qt.GlobalColor.transparent)
painter = QPainter(result)
x = 0
for p in pixmaps:
painter.drawPixmap(x, (h - p.height()) // 2, p)
x += p.width()
painter.drawPixmap(0, (h - left.height()) // 2, left)
painter.drawPixmap(left.width(), (h - right.height()) // 2, right)
painter.end()
return result

View file

@ -1,16 +1,8 @@
from typing import List, Dict, TYPE_CHECKING
from PyQt6.QtWidgets import QMessageBox, QDialog
from PyQt6.QtGui import QIcon
from warchron.constants import (
RefreshScope,
ContextType,
ItemType,
Icons,
IconName,
RANK_TO_ICON,
)
from warchron.constants import RefreshScope, ContextType, ItemType
if TYPE_CHECKING:
from warchron.controller.app_controller import AppController
@ -28,11 +20,10 @@ from warchron.model.campaign_participant import CampaignParticipant
from warchron.model.sector import Sector
from warchron.model.tie_manager import TieContext, TieResolver
from warchron.model.score_service import ScoreService
from warchron.model.result_checker import ResultChecker
from warchron.controller.closure_workflow import CampaignClosureWorkflow
from warchron.view.campaign_dialog import CampaignDialog
from warchron.view.campaign_participant_dialog import CampaignParticipantDialog
from warchron.view.sector_dialog import SectorDialog
from warchron.controller.closure_workflow import CampaignClosureWorkflow
from warchron.view.tie_dialog import TieDialog
@ -40,39 +31,6 @@ class CampaignController:
def __init__(self, app: "AppController"):
self.app = app
def _compute_campaign_ranking_icons(
self, war: War, campaign: Campaign
) -> Dict[str, QIcon]:
scores = ScoreService.compute_scores(
war,
ContextType.CAMPAIGN,
campaign.id,
)
ranking = ResultChecker.get_effective_ranking(
war, ContextType.CAMPAIGN, campaign.id, scores
)
icon_map = {}
for rank, group, token_map in ranking:
base_icon = RANK_TO_ICON.get(rank, IconName.VPNTH)
tie_id = f"{campaign.id}:score:{scores[group[0]].victory_points}"
tie_resolved = TieResolver.is_tie_resolved(
war, ContextType.CAMPAIGN, tie_id
)
for pid in group:
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
def _fill_campaign_details(self, campaign_id: str) -> None:
camp = self.app.model.get_campaign(campaign_id)
self.app.view.show_campaign_details(name=camp.name, month=camp.month)
@ -94,9 +52,6 @@ class CampaignController:
self.app.view.display_campaign_sectors(sectors_for_display)
scores = ScoreService.compute_scores(war, ContextType.CAMPAIGN, campaign_id)
rows: List[CampaignParticipantScoreDTO] = []
icon_map = {}
if camp.is_over:
icon_map = self._compute_campaign_ranking_icons(war, camp)
for camp_part in camp.get_all_campaign_participants():
war_part_id = camp_part.war_participant_id
war_part = war.get_war_participant(war_part_id)
@ -111,7 +66,6 @@ class CampaignController:
theme=camp_part.theme or "",
victory_points=score.victory_points,
narrative_points=dict(score.narrative_points),
rank_icon=icon_map.get(war_part_id),
)
)
objectives = [

View file

@ -1,4 +1,4 @@
from typing import List, Dict
from typing import List
from dataclasses import dataclass
from PyQt6.QtGui import QIcon
@ -112,7 +112,7 @@ class ParticipantScoreDTO:
participant_id: str
player_name: str
victory_points: int
narrative_points: Dict[str, int]
narrative_points: dict[str, int]
@dataclass(frozen=True, slots=True)
@ -123,5 +123,4 @@ class CampaignParticipantScoreDTO:
leader: str
theme: str
victory_points: int
narrative_points: Dict[str, int]
rank_icon: QIcon | None = None
narrative_points: dict[str, int]

View file

@ -3,6 +3,7 @@ from typing import List
from warchron.constants import ContextType
from warchron.model.exception import ForbiddenOperation
from warchron.model.result_checker import ResultChecker
from warchron.model.war_event import InfluenceGained
from warchron.model.war import War
from warchron.model.campaign import Campaign
@ -25,8 +26,6 @@ class ClosureService:
@staticmethod
def apply_battle_outcomes(war: War, campaign: Campaign, battle: Battle) -> None:
from warchron.model.result_checker import ResultChecker
already_granted = any(
isinstance(e, InfluenceGained)
and e.context_id == f"battle:{battle.sector_id}"

View file

@ -1,14 +1,6 @@
from __future__ import annotations
from typing import List, Tuple, Dict, TYPE_CHECKING
from collections import defaultdict
from warchron.constants import ContextType
from warchron.model.war import War
from warchron.model.war_event import TieResolved
from warchron.model.tie_manager import TieResolver
if TYPE_CHECKING:
from warchron.model.score_service import ParticipantScore
class ResultChecker:
@ -30,44 +22,3 @@ class ResultChecker:
return ev.participant_id # None if confirmed draw
return None
@staticmethod
def get_effective_ranking(
war: War,
context_type: ContextType,
context_id: str,
scores: Dict[str, ParticipantScore],
) -> List[Tuple[int, List[str], Dict[str, int]]]:
vp_buckets: Dict[int, List[str]] = defaultdict(list)
for pid, score in scores.items():
vp_buckets[score.victory_points].append(pid)
sorted_vps = sorted(vp_buckets.keys(), reverse=True)
ranking: List[Tuple[int, List[str], Dict[str, int]]] = []
current_rank = 1
for vp in sorted_vps:
participants = vp_buckets[vp]
tie_id = f"{context_id}:score:{vp}"
# no tie
if len(participants) == 1 or not TieResolver.is_tie_resolved(
war, context_type, tie_id
):
ranking.append(
(current_rank, participants, {pid: 0 for pid in participants})
)
current_rank += 1
continue
# apply token ranking
groups = TieResolver.rank_by_tokens(
war,
context_type,
tie_id,
participants,
)
tokens_spent = TieResolver.tokens_spent_map(
war, context_type, tie_id, participants
)
for group in groups:
group_tokens = {pid: tokens_spent[pid] for pid in group}
ranking.append((current_rank, group, group_tokens))
current_rank += 1
return ranking

View file

@ -1,6 +1,7 @@
from typing import Dict, Iterator
from dataclasses import dataclass, field
from warchron.model.result_checker import ResultChecker
from warchron.constants import ContextType
from warchron.model.war import War
from warchron.model.battle import Battle
@ -43,8 +44,6 @@ class ScoreService:
def compute_scores(
war: War, context_type: ContextType, context_id: str
) -> Dict[str, ParticipantScore]:
from warchron.model.result_checker import ResultChecker
scores = {
pid: ParticipantScore(
narrative_points={obj_id: 0 for obj_id in war.objectives}

View file

@ -131,24 +131,6 @@ class TieResolver:
groups[-1].append(pid)
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
def get_active_participants(
war: War,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 841 B

View file

@ -475,11 +475,8 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
table.setColumnCount(len(headers))
table.setHorizontalHeaderLabels(headers)
table.setRowCount(len(participants))
table.setIconSize(QSize(48, 16))
for row, part in enumerate(participants):
name_item = QtWidgets.QTableWidgetItem(part.player_name)
if part.rank_icon:
name_item.setIcon(part.rank_icon)
lead_item = QtWidgets.QTableWidgetItem(part.leader)
theme_item = QtWidgets.QTableWidgetItem(part.theme)
VP_item = QtWidgets.QTableWidgetItem(str(part.victory_points))
@ -557,7 +554,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
table = self.battlesTable
table.clearContents()
table.setRowCount(len(sectors))
table.setIconSize(QSize(32, 16))
self.battlesTable.setIconSize(QSize(32, 16))
for row, battle in enumerate(sectors):
sector_item = QtWidgets.QTableWidgetItem(battle.sector_name)
if battle.state_icon: