from pathlib import Path import json import shutil 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, Choice, Battle class Model: def __init__(self): self.players: dict[str, Player] = {} self.wars: dict[str, War] = {} # File management methods def new(self): self.players.clear() self.wars.clear() def load(self, path: Path): self.players.clear() self.wars.clear() self._load_data(path) def save(self, path: Path): self._save_data(path) def _load_data(self, path: Path): if not path.exists() or path.stat().st_size == 0: return # Start empty try: with open(path, "r", encoding="utf-8") as f: data = json.load(f) self.players.clear() self.wars.clear() for p in data.get("players", []): player = Player.fromDict(p) self.players[player.id] = player for w in data.get("wars", []): war = War.fromDict(w) self.wars[war.id] = war except json.JSONDecodeError: raise RuntimeError("Data file is corrupted") def _save_data(self, path: Path): if path.exists(): shutil.copy(path, path.with_suffix(".json.bak")) data = { "version": "1.0", "players": [p.toDict() for p in self.players.values()], "wars": [w.toDict() for w in self.wars.values()], } with open(path, "w", encoding="utf-8") as f: json.dump(data, f, indent=2) # Player methods def add_player(self, name): player = Player(name) self.players[player.id] = player return player def get_player(self, id): return self.players[id] def get_player_name(self, player_id: str) -> str: return self.players[player_id].name def update_player(self, player_id: str, *, name: str): player = self.get_player(player_id) player.set_name(name) def get_all_players(self) -> list[Player]: 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 def get_default_war_values(self) -> dict: return {"year": datetime.now().year} def add_war(self, name: str, year: int) -> War: war = War(name, year) self.wars[war.id] = war return war def get_war(self, id) -> War: return self.wars[id] def get_war_by_campaign(self, campaign_id: str) -> War: for war in self.wars.values(): for camp in war.campaigns: if camp.id == campaign_id: 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(): if obj.id == objective_id: 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(): if war.has_participant(participant_id): return war raise KeyError(f"Participant {participant_id} not found in any War") def get_war_by_campaign_participant(self, participant_id: str) -> Campaign: for war in self.wars.values(): camp = war.get_campaign_by_campaign_participant(participant_id) if camp is not None: return war raise KeyError(f"Participant {participant_id} not found") def update_war(self, war_id: str, *, name: str, year: int): war = self.get_war(war_id) war.set_name(name) war.set_year(year) def get_all_wars(self) -> list[War]: return list(self.wars.values()) def remove_war(self, war_id: str): del self.wars[war_id] # Objective methods def add_objective(self, war_id: str, name: str, description: str) -> Objective: war = self.get_war(war_id) return war.add_objective(name, description) def get_objective(self, objective_id) -> Objective: for war in self.wars.values(): for obj in war.objectives.values(): if obj.id == objective_id: return obj raise KeyError("Objective not found") def update_objective(self, objective_id: str, *, name: str, description: str): war = self.get_war_by_objective(objective_id) war.update_objective(objective_id, name=name, description=description) def remove_objective(self, objective_id: str): war = self.get_war_by_objective(objective_id) war.remove_objective(objective_id) # War participant methods def get_available_players(self, war_id: str) -> list[Player]: war = self.get_war(war_id) return [ player for player in self.players.values() if not war.has_participant(player.id) ] def add_war_participant( self, war_id: str, player_id: str, faction: str ) -> WarParticipant: war = self.get_war(war_id) return war.add_war_participant(player_id, faction) def get_war_participant(self, participant_id) -> WarParticipant: for war in self.wars.values(): for part in war.participants.values(): if part.id == participant_id: return part raise KeyError("Participant not found") def update_war_participant(self, participant_id: str, *, faction: str): war = self.get_war_by_war_participant(participant_id) war.update_war_participant(participant_id, faction=faction) def remove_war_participant(self, participant_id: str): war = self.get_war_by_war_participant(participant_id) war.remove_war_participant(participant_id) # Campaign methods def get_default_campaign_values(self, war_id: str) -> dict: war = self.get_war(war_id) return war.get_default_campaign_values() def add_campaign(self, war_id: str, name: str, month: int) -> Campaign: war = self.get_war(war_id) return war.add_campaign(name, month) def get_campaign(self, campaign_id) -> Campaign: for war in self.wars.values(): for campaign in war.campaigns: if campaign.id == campaign_id: return campaign raise KeyError("Campaign not found") def get_campaign_by_round(self, round_id: str) -> Campaign: for war in self.wars.values(): camp = war.get_campaign_by_round(round_id) if camp is not None: return camp raise KeyError(f"Round {round_id} not found") 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) if camp is not None: return camp raise KeyError(f"Participant {participant_id} not found") def get_campaign_by_sector(self, sector_id: str) -> Campaign: for war in self.wars.values(): camp = war.get_campaign_by_sector(sector_id) if camp is not None: return camp raise KeyError(f"Sector {sector_id} not found") def update_campaign(self, campaign_id: str, *, name: str, month: int): war = self.get_war_by_campaign(campaign_id) war.update_campaign(campaign_id, name=name, month=month) def remove_campaign(self, campaign_id: str): war = self.get_war_by_campaign(campaign_id) war.remove_campaign(campaign_id) # Sector methods def add_sector( self, campaign_id: str, name: str, round_id: str, major_id: str, minor_id: str, influence_id: str, ) -> Sector: camp = self.get_campaign(campaign_id) return camp.add_sector(name, round_id, major_id, minor_id, influence_id) def get_sector(self, sector_id) -> Sector: for war in self.wars.values(): for camp in war.campaigns: for sect in camp.sectors.values(): if sect.id == sector_id: return sect 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, ): 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) camp.remove_sector(sector_id) # Campaign participant methods def get_available_war_participants(self, campaign_id: str) -> list[WarParticipant]: war = self.get_war_by_campaign(campaign_id) return war.get_available_war_participants(campaign_id) def add_campaign_participant( self, camp_id: str, player_id: str, leader: str, theme: str ) -> CampaignParticipant: camp = self.get_campaign(camp_id) return camp.add_campaign_participant(player_id, leader, theme) def get_campaign_participant(self, participant_id) -> CampaignParticipant: for war in self.wars.values(): for camp in war.campaigns: for part in camp.participants.values(): if part.id == participant_id: return part raise KeyError("Participant not found") def update_campaign_participant( self, participant_id: str, *, leader: str, theme: str, ): war = self.get_war_by_campaign_participant(participant_id) war.update_campaign_participant(participant_id, leader=leader, theme=theme) def remove_campaign_participant(self, participant_id: str): war = self.get_war_by_campaign_participant(participant_id) war.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 get_round(self, round_id: str) -> Round: for war in self.wars.values(): for camp in war.campaigns: for rnd in camp.rounds: if rnd.id == round_id: return rnd raise KeyError("Round not found") 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): war = self.get_war_by_round(round_id) war.remove_round(round_id) # Choice 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 get_round_choices_data(self, round_id: str): camp = self.get_campaign_by_round(round_id) rnd = self.get_round(round_id) participants = camp.participants.values() sectors = [s for s in camp.sectors.values() if s.round_id == round_id] return camp, rnd, participants, sectors def update_choice( self, round_id: str, participant_id: str, priority_sector_id: str | None, secondary_sector_id: str | None, comment: str | None, ): war = self.get_war_by_round(round_id) war.update_choice( round_id, participant_id, priority_sector_id, secondary_sector_id, comment ) 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) # Battle methods def create_battle(self, round_id: str, sector_id: str) -> Battle: war = self.get_war_by_round(round_id) return war.create_battle(round_id, sector_id) def update_battle( self, round_id: str, sector_id: str, player_1_id: str | None, player_2_id: str | None, winner_id: str | None, score: str | None, victory_condition: str | None, comment: str | None, ): war = self.get_war_by_round(round_id) war.update_battle( round_id, sector_id, player_1_id, player_2_id, winner_id, score, victory_condition, comment, ) def remove_battle(self, round_id: str, sector_id: str): war = self.get_war_by_round(round_id) war.remove_battle(round_id, sector_id)