diff --git a/src/warchron/controller/controller.py b/src/warchron/controller/controller.py index 46ef82f..64a4c9a 100644 --- a/src/warchron/controller/controller.py +++ b/src/warchron/controller/controller.py @@ -10,14 +10,14 @@ from warchron.view.view import PlayerDialog, WarDialog, CampaignDialog, Objectiv class Controller: def __init__(self, model: Model, view: View): - self.model = model - self.view = view + self.model: Model = model + self.view: View = view self.current_file: Path | None = None - self.selected_war_id = None - self.selected_campaign_id = None - self.selected_round_id = None + self.selected_war_id: str = None + self.selected_campaign_id: str = None + self.selected_round_id: str = None self.view.on_close_callback = self.on_app_close - self.is_dirty = False + self.is_dirty: bool = False self.__connect() self.refresh_players_view() self.refresh_wars_view() @@ -165,26 +165,24 @@ class Controller: self.view.display_campaign_participants(participants_for_display) def _fill_round_details(self, round_id: str): - index = self.model.get_round_index(round_id) - self.view.show_round_details(index=index) - camp, rnd, participants, sectors = self.model.get_round_choices_data(round_id) - rows = [] + rnd = self.model.get_round(round_id) + camp = self.model.get_campaign_by_round(round_id) + self.view.show_round_details(index=camp.get_round_index(round_id)) + participants = self.model.get_round_participants(round_id) + choices_for_display = [] for part in participants: - player = self.model.get_player(part.id) choice = rnd.get_choice(part.id) - priority_name = "" - secondary_name = "" - if choice and choice.priority_sector_id: - priority_name = self.model.get_sector(choice.priority_sector_id).name - if choice and choice.secondary_sector_id: - secondary_name = self.model.get_sector(choice.secondary_sector_id).name - rows.append({ - "participant_id": part.id, - "participant_name": player.name, - "priority": priority_name, - "secondary": secondary_name, - }) - self.view.display_round_choices(rows) + if not choice: + choice=self.model.create_choice(round_id=rnd.id, participant_id=part.id) + priority_name = camp.get_sector_name(choice.priority_sector_id) if choice else "" + secondary_name = camp.get_sector_name(choice.secondary_sector_id) if choice else "" + choices_for_display.append(( + self.model.get_player_name(part.id), + priority_name, + secondary_name, + choice.participant_id + )) + self.view.display_round_choices(choices_for_display) def on_tree_selection_changed(self, selection): self.selected_war_id = None @@ -578,6 +576,8 @@ class Controller: self.is_dirty = True self.refresh_and_select(RefreshScope.WARS_TREE, item_type=ItemType.ROUND, item_id=rnd.id) +# Choices methods + def edit_round_choice(self, choice_id: str): round_id = self.selected_round_id if not round_id: @@ -586,11 +586,13 @@ class Controller: choice = rnd.get_choice(choice_id) if not choice: return - participant = camp.participants[choice.participant_id] + part = camp.participants[choice.participant_id] + player = self.model.get_player(part.id) + part_opt = ParticipantOption(id=player.id, name=player.name) dialog = ChoicesDialog( self.view, - participants=[participant], - default_participant_id=participant.id, + participants=[part_opt], + default_participant_id=part.id, sectors=sectors, default_priority_id=choice.priority_sector_id, default_secondary_id=choice.secondary_sector_id, @@ -598,7 +600,8 @@ class Controller: if dialog.exec() != QDialog.DialogCode.Accepted: return rnd.set_choice( - participant_id=participant.id, + participant_id=part.id, priority_sector_id=dialog.get_priority_id(), secondary_sector_id=dialog.get_secondary_id(), - ) \ No newline at end of file + ) + diff --git a/src/warchron/model/campaign.py b/src/warchron/model/campaign.py index 9c36808..4a66536 100644 --- a/src/warchron/model/campaign.py +++ b/src/warchron/model/campaign.py @@ -1,7 +1,7 @@ from __future__ import annotations from uuid import uuid4 -from warchron.model.round import Round +from warchron.model.round import Round, Choice, Battle class Campaign: def __init__(self, name: str, month: int): @@ -10,7 +10,7 @@ class Campaign: self.month: int = month self.participants: dict[str, CampaignParticipant] = {} self.sectors: dict[str, Sector] = {} - self.rounds = [] + self.rounds: list[Round] = [] self.is_over = False def set_id(self, new_id: str): @@ -72,6 +72,8 @@ class Campaign: part.set_theme(theme) def remove_campaign_participant(self, player_id: str): + # TODO manage choices referring to it + # TODO manage battles referring to it del self.participants[player_id] # Sector methods @@ -81,12 +83,18 @@ class Campaign: self.sectors[sect.id] = sect return sect - def get_sector(self, id: str) -> Sector: - return self.sectors[id] + def get_sector(self, sector_id: str) -> Sector: + return self.sectors[sector_id] + + def get_sector_name(self, sector_id: str) -> str: + if sector_id is None: + return "" + return self.sectors[sector_id].name def get_all_sectors(self) -> list[Sector]: return list(self.sectors.values()) + # TODO manage choices referring to it (round order!) def update_sector(self, sector_id: str, *, name: str, round_id: str, major_id: str, minor_id: str, influence_id: str): sect = self.get_sector(sector_id) sect.set_name(name) @@ -96,6 +104,8 @@ class Campaign: sect.set_influence(influence_id) def remove_sector(self, sector_id: str): + # TODO manage choices referring to it + # TODO manage battles referring to it del self.sectors[sector_id] # Round methods @@ -104,7 +114,10 @@ class Campaign: return any(r.id == round_id for r in self.rounds) def get_round(self, round_id: str) -> Round: - return self.rounds[round_id] + for rnd in self.rounds: + if rnd.id == round_id: + return rnd + raise KeyError(f"Round {round_id} not found") def get_all_rounds(self) -> list[Round]: return list(self.rounds) @@ -135,6 +148,16 @@ class Campaign: return rnd.name return "" +# Choice methods + + def create_choice(self, round_id: str, participant_id: str) -> Choice: + rnd = self.get_round(round_id) + return rnd.create_choice(participant_id) + + def remove_choice(self, round_id: str, participant_id: str) -> Choice: + rnd = self.get_round(round_id) + rnd.remove_choice(participant_id) + class CampaignParticipant: def __init__(self,player_id: str, leader: str, theme: str): self.id: str = player_id # ref to War.participants diff --git a/src/warchron/model/model.py b/src/warchron/model/model.py index 4ed5087..85713a6 100644 --- a/src/warchron/model/model.py +++ b/src/warchron/model/model.py @@ -6,7 +6,7 @@ from datetime import datetime from warchron.model.player import Player from warchron.model.war import War, Objective, WarParticipant from warchron.model.campaign import Campaign, Sector, CampaignParticipant -from warchron.model.round import Round +from warchron.model.round import Round, Choice, Battle class Model: def __init__(self): @@ -76,6 +76,7 @@ class Model: return list(self.players.values()) def remove_player(self, player_id: str): + # TODO manage war_participants referring to it del self.players[player_id] # War methods @@ -100,6 +101,22 @@ class Model: return war raise KeyError(f"Campaign {campaign_id} not found in any War") + def get_war_by_sector(self, sector_id: str) -> Campaign: + for war in self.wars.values(): + for camp in war.campaigns: + for sect in camp.sectors.values(): + if sect.id == sector_id: + return camp + raise KeyError(f"Sector {sector_id} not found in any War") + + def get_war_by_round(self, round_id: str) -> Campaign: + for war in self.wars.values(): + for camp in war.campaigns: + for rnd in camp.rounds: + if rnd.id == round_id: + return camp + raise KeyError(f"Round {round_id} not found in any War") + def get_war_by_objective(self, objective_id: str) -> War: for war in self.wars.values(): for obj in war.objectives.values(): @@ -107,6 +124,7 @@ class Model: return war raise KeyError(f"Objective {objective_id} not found in any War") + # TODO don't use this method as participant with same ID (player) can be in several wars! def get_war_by_war_participant(self, participant_id: str) -> War: for war in self.wars.values(): for part in war.participants.values(): @@ -199,6 +217,7 @@ class Model: return camp raise KeyError(f"Round {round_id} not found") + # TODO don't use this method as participant with same ID (player) can be in several campaigns! def get_campaign_by_campaign_participant(self, participant_id: str) -> Campaign: for war in self.wars.values(): camp = war.get_campaign_by_campaign_participant(participant_id) @@ -236,8 +255,8 @@ class Model: raise KeyError("Sector not found") def update_sector(self, sector_id: str, *, name: str, round_id: str, major_id: str, minor_id: str, influence_id: str): - camp = self.get_campaign_by_sector(sector_id) - camp.update_sector(sector_id, name=name, round_id=round_id, major_id=major_id, minor_id=minor_id, influence_id=influence_id) + war = self.get_war_by_sector(sector_id) + war.update_sector(sector_id, name=name, round_id=round_id, major_id=major_id, minor_id=minor_id, influence_id=influence_id) def remove_sector(self, sector_id: str): camp = self.get_campaign_by_sector(sector_id) @@ -286,13 +305,29 @@ class Model: def get_round_index(self, round_id: str) -> int: camp = self.get_campaign_by_round(round_id) return camp.get_round_index(round_id) + + def get_round_sectors(self, round_id: str) -> list[Sector]: + camp = self.get_campaign_by_round(round_id) + return [s for s in camp.sectors.values() if s.round_id == round_id] + + def get_round_participants(self, round_id: str) -> list[CampaignParticipant]: + camp = self.get_campaign_by_round(round_id) + return list(camp.participants.values()) def remove_round(self, round_id: str): - camp = self.get_campaign_by_round(round_id) - camp.remove_round(round_id) + war = self.get_war_by_round(round_id) + war.remove_round(round_id) # Choices methods + def create_choice(self, round_id: str, participant_id: str) -> Choice: + war = self.get_war_by_round(round_id) + return war.create_choice(round_id, participant_id) + + def remove_choice(self, round_id: str, participant_id: str): + war = self.get_war_by_round(round_id) + war.remove_choice(round_id, participant_id) + def get_round_choices_data(self, round_id: str): camp = self.get_campaign_by_round(round_id) rnd = self.get_round(round_id) diff --git a/src/warchron/model/round.py b/src/warchron/model/round.py index e4c2173..d388856 100644 --- a/src/warchron/model/round.py +++ b/src/warchron/model/round.py @@ -4,8 +4,8 @@ from uuid import uuid4 class Round: def __init__(self): self.id: str = str(uuid4()) - self.choices: dict[str, RoundChoice] = {} - self.battles = {} + self.choices: dict[str, Choice] = {} + self.battles: dict[str, Battle] = {} self.is_over: bool = False def set_id(self, new_id: str): @@ -15,7 +15,7 @@ class Round: self.is_over = new_state def set_choice(self, participant_id: str, priority_sector_id: str | None, secondary_sector_id: str | None): - self.choices[participant_id] = RoundChoice(participant_id, priority_sector_id, secondary_sector_id) + self.choices[participant_id] = Choice(participant_id, priority_sector_id, secondary_sector_id) def toDict(self): return { @@ -38,12 +38,30 @@ class Round: # Choices methods - def get_choice(self, participant_id: str) -> RoundChoice | None: + def get_choice(self, participant_id: str) -> Choice | None: return self.choices.get(participant_id) + def create_choice(self, participant_id: str) -> Choice: + if participant_id not in self.choices: + choice = Choice( + participant_id=participant_id, + priority_sector_id=None, + secondary_sector_id=None + ) + self.choices[participant_id] = choice + return self.choices[participant_id] -class RoundChoice: + def remove_choice(self,participant_id: str): + del self.choices[participant_id] + +class Choice: def __init__(self, participant_id: str, priority_sector_id: str | None = None, secondary_sector_id: str | None = None): self.participant_id: str = participant_id # ref to Campaign.participants self.priority_sector_id: str | None = priority_sector_id # ref to Campaign.sectors self.secondary_sector_id: str | None = secondary_sector_id # ref to Campaign.sectors + +class Battle: + def __init__(self, sector_id: str, player_1_id: str | None = None, player_2_id: str | None = None): + self.sector_id: str = sector_id # ref to Campaign.sector + self.player_1_id: str | None = player_1_id # ref to Campaign.participants + self.player_2_id: str | None = player_2_id # ref to Campaign.participants diff --git a/src/warchron/model/war.py b/src/warchron/model/war.py index db14c78..8de9951 100644 --- a/src/warchron/model/war.py +++ b/src/warchron/model/war.py @@ -3,6 +3,7 @@ from uuid import uuid4 from datetime import datetime from warchron.model.campaign import Campaign, Sector, CampaignParticipant +from warchron.model.round import Round, Choice, Battle class War: @@ -12,7 +13,7 @@ class War: self.year: int = year self.participants: dict[str, WarParticipant] = {} self.objectives: dict[str, Objective] = {} - self.campaigns = [] + self.campaigns: list[Campaign] = [] self.is_over: bool = False def set_id(self, new_id: str): @@ -72,6 +73,7 @@ class War: obj.set_description(description) def remove_objective(self, objective_id: str): + # TODO manage sectors referring to it del self.objectives[objective_id] # War participant methods @@ -100,6 +102,7 @@ class War: part.set_faction(faction) def remove_war_participant(self, player_id: str): + # TODO manage campaign_participants referring to it del self.participants[player_id] # Campaign methods @@ -167,10 +170,10 @@ class War: def get_sector(self, id: str) -> Sector: return self.sectors[id] - def update_sector(self, objective_id: str, *, name: str, round_id: str, major_id: str, minor_id: str, influence_id: str): - obj = self.get_objective(objective_id) - obj.set_name(name) - + def update_sector(self, sector_id: str, *, name: str, round_id: str, major_id: str, minor_id: str, influence_id: str): + camp = self.get_campaign_by_sector(sector_id) + camp.update_sector(sector_id, name=name, round_id=round_id, major_id=major_id, minor_id=minor_id, influence_id=influence_id) + def remove_sector(self, sector_id: str): camp = self.get_campaign_by_sector(sector_id) camp.remove_sector(sector_id) @@ -204,6 +207,26 @@ class War: camp = self.get_campaign_by_campaign_participant(participant_id) camp.remove_campaign_participant(participant_id) +# Round methods + + def add_round(self, campaign_id: str) -> Round: + camp = self.get_campaign(campaign_id) + return camp.add_round() + + def remove_round(self, round_id: str): + camp = self.get_campaign_by_round(round_id) + camp.remove_round(round_id) + +# Choice methods + + def create_choice(self, round_id: str, participant_id: str) -> Choice: + camp = self.get_campaign_by_round(round_id) + return camp.create_choice(round_id, participant_id) + + def remove_choice(self, round_id: str, participant_id: str): + camp = self.get_campaign_by_round(round_id) + camp.remove_choice(round_id, participant_id) + class Objective: def __init__(self, name: str, description: str): self.id: str = str(uuid4()) diff --git a/src/warchron/view/view.py b/src/warchron/view/view.py index 1ac1198..2f3a452 100644 --- a/src/warchron/view/view.py +++ b/src/warchron/view/view.py @@ -387,6 +387,8 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): if not name_item: return choice_id = name_item.data(Qt.ItemDataRole.UserRole) + if choice_id is None: + return menu = QMenu(self) edit_action = menu.addAction("Edit") action = menu.exec(self.choicesTable.viewport().mapToGlobal(pos)) @@ -396,16 +398,19 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): def show_round_details(self, *, index: int): self.roundNb.setText(f"Round {index}") - def display_round_choices(self, rows: list[dict]): - self.choicesTable.setRowCount(len(rows)) - for row, data in enumerate(rows): - self.choicesTable.setItem(row, 0, QtWidgets.QTableWidgetItem(data["participant_name"])) - self.choicesTable.setItem(row, 1, QtWidgets.QTableWidgetItem(data["priority"])) - self.choicesTable.setItem(row, 2, QtWidgets.QTableWidgetItem(data["secondary"])) - self.choicesTable.item(row, 0).setData( - Qt.ItemDataRole.UserRole, - data["participant_id"] - ) + def display_round_choices(self, participants: list[tuple[str, str, str, str]]): + table = self.choicesTable + table.clearContents() + table.setRowCount(len(participants)) + for row, (participant, priority, secondary, choice_id) in enumerate(participants): + participant_item = QtWidgets.QTableWidgetItem(participant) + priority_item = QtWidgets.QTableWidgetItem(priority) + secondary_item = QtWidgets.QTableWidgetItem(secondary) + participant_item.setData(Qt.ItemDataRole.UserRole, choice_id) + table.setItem(row, 0, participant_item) + table.setItem(row, 1, priority_item) + table.setItem(row, 2, secondary_item) + table.resizeColumnsToContents() class PlayerDialog(QDialog): def __init__(self, parent=None, *, default_name: str = ""):