from typing import List, Dict, TYPE_CHECKING from PyQt6.QtWidgets import QDialog from PyQt6.QtWidgets import QMessageBox from warchron.constants import ItemType, RefreshScope, Icons, IconName from warchron.model.exception import ForbiddenOperation, DomainError from warchron.model.round import Round from warchron.model.war import War if TYPE_CHECKING: from warchron.controller.app_controller import AppController from warchron.controller.dtos import ( ParticipantOption, SectorDTO, ChoiceDTO, BattleDTO, TieContext, ) from warchron.controller.closure_workflow import RoundClosureWorkflow from warchron.view.choice_dialog import ChoiceDialog from warchron.view.battle_dialog import BattleDialog from warchron.view.tie_dialog import TieDialog class RoundController: def __init__(self, app: "AppController"): self.app = app def _fill_round_details(self, round_id: str) -> None: rnd = self.app.model.get_round(round_id) camp = self.app.model.get_campaign_by_round(round_id) self.app.view.show_round_details(index=camp.get_round_index(round_id)) participants = self.app.model.get_round_participants(round_id) sectors = camp.get_sectors_in_round(round_id) choices_for_display: List[ChoiceDTO] = [] for part in participants: choice = rnd.get_choice(part.id) if not choice: choice = self.app.model.create_choice( round_id=rnd.id, participant_id=part.id ) priority_name = ( camp.get_sector_name(choice.priority_sector_id) if choice.priority_sector_id is not None else "" ) secondary_name = ( camp.get_sector_name(choice.secondary_sector_id) if choice.secondary_sector_id is not None else "" ) choices_for_display.append( ChoiceDTO( id=choice.participant_id, participant_name=self.app.model.get_participant_name( part.war_participant_id ), priority_sector=priority_name, secondary_sector=secondary_name, comment=choice.comment, ) ) self.app.view.display_round_choices(choices_for_display) battles_for_display: List[BattleDTO] = [] for sect in sectors: battle = rnd.get_battle(sect.id) if not battle: battle = self.app.model.create_battle( round_id=rnd.id, sector_id=sect.id ) state_icon = Icons.get(IconName.ONGOING) if battle.is_finished(): state_icon = Icons.get(IconName.DONE) if battle.player_1_id: camp_part = camp.participants[battle.player_1_id] player_1_name = self.app.model.get_participant_name( camp_part.war_participant_id ) else: player_1_name = "" if battle.player_2_id: camp_part = camp.participants[battle.player_2_id] player_2_name = self.app.model.get_participant_name( camp_part.war_participant_id ) else: player_2_name = "" if battle.winner_id: camp_part = camp.participants[battle.winner_id] winner_name = self.app.model.get_participant_name( camp_part.war_participant_id ) else: winner_name = "" p1_icon = None p2_icon = None if battle.is_draw(): p1_icon = Icons.get(IconName.DRAW) p2_icon = Icons.get(IconName.DRAW) elif battle.winner_id: if battle.winner_id == battle.player_1_id: p1_icon = Icons.get(IconName.WIN) elif battle.winner_id == battle.player_2_id: p2_icon = Icons.get(IconName.WIN) battles_for_display.append( BattleDTO( id=battle.sector_id, sector_name=camp.get_sector_name(battle.sector_id), player_1=player_1_name, player_2=player_2_name, winner=winner_name, score=battle.score, victory_condition=battle.victory_condition, comment=battle.comment, state_icon=state_icon, player1_icon=p1_icon, player2_icon=p2_icon, ) ) self.app.view.display_round_battles(battles_for_display) self.app.view.endRoundBtn.setEnabled(not rnd.is_over) def create_round(self) -> Round | None: campaign_id = self.app.navigation.selected_campaign_id if not campaign_id: return None return self.app.model.add_round(campaign_id) def close_round(self) -> None: round_id = self.app.navigation.selected_round_id if not round_id: return rnd = self.app.model.get_round(round_id) camp = self.app.model.get_campaign_by_round(round_id) war = self.app.model.get_war_by_round(round_id) workflow = RoundClosureWorkflow(self.app) try: workflow.start(war, camp, rnd) except DomainError as e: QMessageBox.warning( self.app.view, "Deletion forbidden", str(e), ) self.app.is_dirty = True self.app.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS) self.app.navigation.refresh_and_select( RefreshScope.WARS_TREE, item_type=ItemType.ROUND, item_id=round_id ) def resolve_ties( self, war: War, contexts: List[TieContext] ) -> Dict[str, Dict[str, bool]]: bids_map = {} for ctx in contexts: players = [ ParticipantOption( id=pid, name=self.app.model.get_participant_name(pid), ) for pid in ctx.participants ] counters = [war.get_influence_tokens(pid) for pid in ctx.participants] dialog = TieDialog( parent=self.app.view, players=players, counters=counters, context_id=ctx.context_id, ) if not dialog.exec(): raise ForbiddenOperation("Tie resolution cancelled") bids_map[ctx.context_id] = dialog.get_bids() return bids_map # Choice methods def edit_round_choice(self, choice_id: str) -> None: round_id = self.app.navigation.selected_round_id if not round_id: return war = self.app.model.get_war_by_round(round_id) camp = self.app.model.get_campaign_by_round(round_id) rnd = camp.get_round(round_id) sectors = camp.get_sectors_in_round(round_id) sect_opts: List[SectorDTO] = [ SectorDTO( id=sect.id, name=sect.name, round_index=camp.get_round_index(sect.round_id), major=war.get_objective_name(sect.major_objective_id), minor=war.get_objective_name(sect.minor_objective_id), influence=war.get_objective_name(sect.influence_objective_id), mission=sect.mission, description=sect.description, ) for sect in sectors ] choice = rnd.get_choice(choice_id) if not choice: return participant = camp.participants[choice.participant_id] player = self.app.model.get_player_from_campaign_participant(participant) part_opt = ParticipantOption(id=participant.id, name=player.name) dialog = ChoiceDialog( self.app.view, participants=[part_opt], default_participant_id=participant.id, sectors=sect_opts, default_priority_id=choice.priority_sector_id, default_secondary_id=choice.secondary_sector_id, default_comment=choice.comment, ) if dialog.exec() != QDialog.DialogCode.Accepted: return self.app.model.update_choice( round_id=round_id, participant_id=participant.id, priority_sector_id=dialog.get_priority_id(), secondary_sector_id=dialog.get_secondary_id(), comment=dialog.get_comment(), ) # Battle methods def edit_round_battle(self, battle_id: str) -> None: round_id = self.app.navigation.selected_round_id if not round_id: return war = self.app.model.get_war_by_round(round_id) camp = self.app.model.get_campaign_by_round(round_id) rnd = camp.get_round(round_id) participants = camp.get_all_campaign_participants() battle = rnd.get_battle(battle_id) if not battle: return sect = camp.sectors[battle.sector_id] sect_dto = SectorDTO( id=sect.id, name=sect.name, round_index=camp.get_round_index(sect.round_id), major=war.get_objective_name(sect.major_objective_id), minor=war.get_objective_name(sect.minor_objective_id), influence=war.get_objective_name(sect.influence_objective_id), mission=sect.mission, description=sect.description, ) part_opts: List[ParticipantOption] = [] for participant in participants: player = self.app.model.get_player_from_campaign_participant(participant) part_opts.append(ParticipantOption(id=participant.id, name=player.name)) dialog = BattleDialog( self.app.view, sectors=[sect_dto], default_sector_id=sect.id, players=part_opts, default_player_1_id=battle.player_1_id, default_player_2_id=battle.player_2_id, default_winner_id=battle.winner_id, default_score=battle.score, default_victory_condition=battle.victory_condition, default_comment=battle.comment, ) if dialog.exec() != QDialog.DialogCode.Accepted: return self.app.model.update_battle( round_id=round_id, sector_id=sect.id, player_1_id=dialog.get_player_1_id(), player_2_id=dialog.get_player_2_id(), winner_id=dialog.get_winner_id(), score=dialog.get_score(), victory_condition=dialog.get_victory_condition(), comment=dialog.get_comment(), )