display battle tie-break token
This commit is contained in:
parent
818d2886f4
commit
23110383c2
9 changed files with 127 additions and 29 deletions
|
|
@ -4,7 +4,7 @@ from pathlib import Path
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from PyQt6.QtCore import Qt
|
from PyQt6.QtCore import Qt
|
||||||
from PyQt6.QtGui import QIcon
|
from PyQt6.QtGui import QIcon, QPixmap, QPainter
|
||||||
|
|
||||||
|
|
||||||
# Paths
|
# Paths
|
||||||
|
|
@ -17,37 +17,44 @@ ROLE_ID = Qt.ItemDataRole.UserRole + 1
|
||||||
|
|
||||||
|
|
||||||
class IconName(str, Enum):
|
class IconName(str, Enum):
|
||||||
UNDO = ("undo",)
|
UNDO = "undo"
|
||||||
REDO = ("redo",)
|
REDO = "redo"
|
||||||
PAIRING = ("pairing",)
|
PAIRING = "pairing"
|
||||||
DRAW = ("draw",)
|
DRAW = "draw"
|
||||||
DELETE = ("delete",)
|
TIEBREAK = "tie-break"
|
||||||
SAVE_AS = ("save_as",)
|
DELETE = "delete"
|
||||||
SAVE = ("save",)
|
SAVE_AS = "save_as"
|
||||||
NEW = ("new",)
|
SAVE = "save"
|
||||||
EXIT = ("exit",)
|
NEW = "new"
|
||||||
END = ("end",)
|
EXIT = "exit"
|
||||||
OPEN = ("load",)
|
END = "end"
|
||||||
ONGOING = ("ongoing",)
|
OPEN = "load"
|
||||||
EXPORT = ("export",)
|
ONGOING = "ongoing"
|
||||||
EDIT = ("edit",)
|
EXPORT = "export"
|
||||||
ADD = ("add",)
|
EDIT = "edit"
|
||||||
ABOUT = ("about",)
|
ADD = "add"
|
||||||
WARS = ("wars",)
|
ABOUT = "about"
|
||||||
DONE = ("done",)
|
WARS = "wars"
|
||||||
WIN = ("win",)
|
DONE = "done"
|
||||||
PLAYERS = ("players",)
|
WIN = "win"
|
||||||
|
PLAYERS = "players"
|
||||||
WARCHRON = "warchron"
|
WARCHRON = "warchron"
|
||||||
|
TOKEN = "token"
|
||||||
|
TOKENS = "tokens"
|
||||||
|
TIEBREAK_TOKEN = auto()
|
||||||
|
|
||||||
|
|
||||||
class Icons:
|
class Icons:
|
||||||
_cache: Dict[str, QIcon] = {}
|
_icon_cache: Dict[IconName, QIcon] = {}
|
||||||
|
|
||||||
|
_pixmap_cache: Dict[IconName, QPixmap] = {}
|
||||||
|
|
||||||
_paths = {
|
_paths = {
|
||||||
IconName.UNDO: "arrow-curve-180-left",
|
IconName.UNDO: "arrow-curve-180-left",
|
||||||
IconName.REDO: "arrow-curve",
|
IconName.REDO: "arrow-curve",
|
||||||
IconName.PAIRING: "arrow-switch",
|
IconName.PAIRING: "arrow-switch",
|
||||||
IconName.DRAW: "balance.png",
|
IconName.DRAW: "balance.png",
|
||||||
|
IconName.TIEBREAK: "balance-unbalance.png",
|
||||||
IconName.DELETE: "cross.png",
|
IconName.DELETE: "cross.png",
|
||||||
IconName.SAVE_AS: "disk--pencil.png",
|
IconName.SAVE_AS: "disk--pencil.png",
|
||||||
IconName.SAVE: "disk.png",
|
IconName.SAVE: "disk.png",
|
||||||
|
|
@ -65,14 +72,43 @@ class Icons:
|
||||||
IconName.WIN: "trophy.png",
|
IconName.WIN: "trophy.png",
|
||||||
IconName.PLAYERS: "users.png",
|
IconName.PLAYERS: "users.png",
|
||||||
IconName.WARCHRON: "warchron_logo.png",
|
IconName.WARCHRON: "warchron_logo.png",
|
||||||
|
IconName.TOKEN: "point.png",
|
||||||
|
IconName.TOKENS: "points.png",
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, name: IconName) -> QIcon:
|
def get(cls, name: IconName) -> QIcon:
|
||||||
if name not in cls._cache:
|
if name not in cls._icon_cache:
|
||||||
path = RESOURCES_DIR / cls._paths[name]
|
path = RESOURCES_DIR / cls._paths[name]
|
||||||
cls._cache[name] = QIcon(str(path))
|
cls._icon_cache[name] = QIcon(str(path))
|
||||||
return cls._cache[name]
|
return cls._icon_cache[name]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_pixmap(cls, name: IconName) -> QPixmap:
|
||||||
|
if name in cls._pixmap_cache:
|
||||||
|
return cls._pixmap_cache[name]
|
||||||
|
if name == IconName.TIEBREAK_TOKEN:
|
||||||
|
pix = cls._compose(
|
||||||
|
cls.get_pixmap(IconName.TIEBREAK),
|
||||||
|
cls.get_pixmap(IconName.TOKEN),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
path = RESOURCES_DIR / cls._paths[name]
|
||||||
|
pix = QPixmap(path.as_posix())
|
||||||
|
cls._pixmap_cache[name] = pix
|
||||||
|
return pix
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
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)
|
||||||
|
painter.drawPixmap(0, (h - left.height()) // 2, left)
|
||||||
|
painter.drawPixmap(left.width(), (h - right.height()) // 2, right)
|
||||||
|
painter.end()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class ItemType(StrEnum):
|
class ItemType(StrEnum):
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,8 @@ 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
|
@dataclass
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@ from typing import List, Dict, TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt6.QtWidgets import QDialog
|
from PyQt6.QtWidgets import QDialog
|
||||||
from PyQt6.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
|
from PyQt6.QtGui import QIcon
|
||||||
|
|
||||||
from warchron.constants import ItemType, RefreshScope, Icons, IconName
|
from warchron.constants import ItemType, RefreshScope, Icons, IconName, ContextType
|
||||||
from warchron.model.exception import ForbiddenOperation, DomainError
|
from warchron.model.exception import ForbiddenOperation, DomainError
|
||||||
|
from warchron.model.tie_manager import TieResolver
|
||||||
from warchron.model.round import Round
|
from warchron.model.round import Round
|
||||||
from warchron.model.war import War
|
from warchron.model.war import War
|
||||||
|
|
||||||
|
|
@ -31,6 +33,7 @@ class RoundController:
|
||||||
def _fill_round_details(self, round_id: str) -> None:
|
def _fill_round_details(self, round_id: str) -> None:
|
||||||
rnd = self.app.model.get_round(round_id)
|
rnd = self.app.model.get_round(round_id)
|
||||||
camp = self.app.model.get_campaign_by_round(round_id)
|
camp = self.app.model.get_campaign_by_round(round_id)
|
||||||
|
war = self.app.model.get_war_by_round(round_id)
|
||||||
self.app.view.show_round_details(index=camp.get_round_index(round_id))
|
self.app.view.show_round_details(index=camp.get_round_index(round_id))
|
||||||
participants = self.app.model.get_round_participants(round_id)
|
participants = self.app.model.get_round_participants(round_id)
|
||||||
sectors = camp.get_sectors_in_round(round_id)
|
sectors = camp.get_sectors_in_round(round_id)
|
||||||
|
|
@ -97,9 +100,29 @@ class RoundController:
|
||||||
winner_name = ""
|
winner_name = ""
|
||||||
p1_icon = None
|
p1_icon = None
|
||||||
p2_icon = None
|
p2_icon = None
|
||||||
|
p1_tooltip = None
|
||||||
|
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)
|
||||||
|
if TieResolver.was_tie_broken_by_tokens(
|
||||||
|
war, ContextType.BATTLE, battle.sector_id
|
||||||
|
):
|
||||||
|
effective_winner = TieResolver.get_effective_winner_id(
|
||||||
|
war, ContextType.BATTLE, battle.sector_id, None
|
||||||
|
)
|
||||||
|
p1_war = None
|
||||||
|
if battle.player_1_id is not None:
|
||||||
|
p1_war = camp.participants[
|
||||||
|
battle.player_1_id
|
||||||
|
].war_participant_id
|
||||||
|
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:
|
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)
|
||||||
|
|
@ -118,6 +141,8 @@ 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)
|
||||||
|
|
@ -168,6 +193,7 @@ class RoundController:
|
||||||
parent=self.app.view,
|
parent=self.app.view,
|
||||||
players=players,
|
players=players,
|
||||||
counters=counters,
|
counters=counters,
|
||||||
|
context_type=ContextType.BATTLE,
|
||||||
context_id=ctx.context_id,
|
context_id=ctx.context_id,
|
||||||
)
|
)
|
||||||
if not dialog.exec():
|
if not dialog.exec():
|
||||||
|
|
|
||||||
|
|
@ -106,3 +106,18 @@ class TieResolver:
|
||||||
return ev.participant_id # None if confirmed draw
|
return ev.participant_id # None if confirmed draw
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def was_tie_broken_by_tokens(
|
||||||
|
war: War,
|
||||||
|
context_type: ContextType,
|
||||||
|
context_id: str,
|
||||||
|
) -> bool:
|
||||||
|
for ev in reversed(war.events):
|
||||||
|
if (
|
||||||
|
isinstance(ev, TieResolved)
|
||||||
|
and ev.context_type == context_type
|
||||||
|
and ev.context_id == context_id
|
||||||
|
):
|
||||||
|
return ev.participant_id is not None
|
||||||
|
return False
|
||||||
|
|
|
||||||
BIN
src/warchron/view/resources/balance-unbalance.png
Normal file
BIN
src/warchron/view/resources/balance-unbalance.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 806 B |
BIN
src/warchron/view/resources/point.png
Normal file
BIN
src/warchron/view/resources/point.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 832 B |
BIN
src/warchron/view/resources/points.png
Normal file
BIN
src/warchron/view/resources/points.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 754 B |
|
|
@ -1,8 +1,9 @@
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
from PyQt6.QtWidgets import QWidget, QDialog
|
from PyQt6.QtWidgets import QWidget, QDialog
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
|
|
||||||
from warchron.constants import Icons, IconName
|
from warchron.constants import Icons, IconName, ContextType, RESOURCES_DIR
|
||||||
from warchron.controller.dtos import ParticipantOption
|
from warchron.controller.dtos import ParticipantOption
|
||||||
from warchron.view.ui.ui_tie_dialog import Ui_tieDialog
|
from warchron.view.ui.ui_tie_dialog import Ui_tieDialog
|
||||||
|
|
||||||
|
|
@ -14,6 +15,7 @@ class TieDialog(QDialog):
|
||||||
*,
|
*,
|
||||||
players: List[ParticipantOption],
|
players: List[ParticipantOption],
|
||||||
counters: List[int],
|
counters: List[int],
|
||||||
|
context_type: ContextType,
|
||||||
context_id: str,
|
context_id: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
@ -22,7 +24,13 @@ class TieDialog(QDialog):
|
||||||
self._p2_id = players[1].id
|
self._p2_id = players[1].id
|
||||||
self.ui: Ui_tieDialog = Ui_tieDialog()
|
self.ui: Ui_tieDialog = Ui_tieDialog()
|
||||||
self.ui.setupUi(self) # type: ignore
|
self.ui.setupUi(self) # type: ignore
|
||||||
self.ui.tieContext.setText("Battle tie") # Change with context
|
self.ui.tieContext.setText(self._get_context_title(context_type))
|
||||||
|
icon_path = (RESOURCES_DIR / Icons._paths[IconName.TOKENS]).as_posix()
|
||||||
|
html = f'<img src="{icon_path}" width="16" height="16"> Remaining token(s)'
|
||||||
|
self.ui.label_2.setText(html)
|
||||||
|
self.ui.label_2.setTextFormat(Qt.TextFormat.RichText)
|
||||||
|
self.ui.label_3.setText(html)
|
||||||
|
self.ui.label_3.setTextFormat(Qt.TextFormat.RichText)
|
||||||
self.ui.groupBox_1.setTitle(players[0].name)
|
self.ui.groupBox_1.setTitle(players[0].name)
|
||||||
self.ui.groupBox_2.setTitle(players[1].name)
|
self.ui.groupBox_2.setTitle(players[1].name)
|
||||||
self.ui.tokenCount_1.setText(str(counters[0]))
|
self.ui.tokenCount_1.setText(str(counters[0]))
|
||||||
|
|
@ -38,3 +46,13 @@ class TieDialog(QDialog):
|
||||||
self._p1_id: self.ui.tokenSpend_1.isChecked(),
|
self._p1_id: self.ui.tokenSpend_1.isChecked(),
|
||||||
self._p2_id: self.ui.tokenSpend_2.isChecked(),
|
self._p2_id: self.ui.tokenSpend_2.isChecked(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_context_title(context_type: ContextType) -> str:
|
||||||
|
titles = {
|
||||||
|
ContextType.BATTLE: "Battle tie",
|
||||||
|
ContextType.CAMPAIGN: "Campaign tie",
|
||||||
|
ContextType.WAR: "War tie",
|
||||||
|
ContextType.CHOICE: "Choice tie",
|
||||||
|
}
|
||||||
|
return titles.get(context_type, "Tie")
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from pathlib import Path
|
||||||
import calendar
|
import calendar
|
||||||
|
|
||||||
from PyQt6 import QtWidgets
|
from PyQt6 import QtWidgets
|
||||||
from PyQt6.QtCore import Qt, QPoint
|
from PyQt6.QtCore import Qt, QPoint, QSize
|
||||||
from PyQt6.QtWidgets import QWidget, QFileDialog, QTreeWidgetItem, QMenu
|
from PyQt6.QtWidgets import QWidget, QFileDialog, QTreeWidgetItem, QMenu
|
||||||
from PyQt6.QtGui import QCloseEvent
|
from PyQt6.QtGui import QCloseEvent
|
||||||
|
|
||||||
|
|
@ -540,6 +540,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||||
table = self.battlesTable
|
table = self.battlesTable
|
||||||
table.clearContents()
|
table.clearContents()
|
||||||
table.setRowCount(len(sectors))
|
table.setRowCount(len(sectors))
|
||||||
|
self.battlesTable.setIconSize(QSize(32, 16))
|
||||||
for row, battle in enumerate(sectors):
|
for row, battle in enumerate(sectors):
|
||||||
sector_item = QtWidgets.QTableWidgetItem(battle.sector_name)
|
sector_item = QtWidgets.QTableWidgetItem(battle.sector_name)
|
||||||
if battle.state_icon:
|
if battle.state_icon:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue