warchron_app/src/warchron/model/round.py

204 lines
7.5 KiB
Python
Raw Normal View History

2026-01-30 10:52:19 +01:00
from __future__ import annotations
2026-02-02 10:41:16 +01:00
from uuid import uuid4
from typing import Any, Dict, List, TYPE_CHECKING
2026-02-02 10:41:16 +01:00
if TYPE_CHECKING:
from warchron.model.campaign import Campaign
from warchron.model.exception import ForbiddenOperation
from warchron.model.choice import Choice
from warchron.model.battle import Battle
2026-01-21 07:43:04 +01:00
2026-01-19 18:55:07 +01:00
class Round:
2026-02-04 16:10:53 +01:00
def __init__(self) -> None:
self.id: str = str(uuid4())
2026-02-04 16:10:53 +01:00
self.choices: Dict[str, Choice] = {}
self.battles: Dict[str, Battle] = {}
self.is_over: bool = False
self._campaign: Campaign | None = None # private link
2026-01-19 18:55:07 +01:00
2026-02-04 16:10:53 +01:00
def set_id(self, new_id: str) -> None:
2026-01-21 07:43:04 +01:00
self.id = new_id
2026-01-19 18:55:07 +01:00
2026-02-04 16:10:53 +01:00
def set_state(self, new_state: bool) -> None:
2026-01-19 18:55:07 +01:00
self.is_over = new_state
2026-02-04 16:10:53 +01:00
def toDict(self) -> Dict[str, Any]:
2026-01-19 18:55:07 +01:00
return {
2026-01-21 08:31:48 +01:00
"id": self.id,
"choices": [c.toDict() for c in self.choices.values()],
"battles": [b.toDict() for b in self.battles.values()],
2026-02-02 10:41:16 +01:00
"is_over": self.is_over,
2026-01-19 18:55:07 +01:00
}
@staticmethod
2026-02-04 16:10:53 +01:00
def fromDict(data: Dict[str, Any]) -> Round:
2026-01-21 08:31:48 +01:00
rnd = Round()
rnd.set_id(data["id"])
for c in data.get("choices", []):
choice = Choice.fromDict(c)
rnd.choices[choice.participant_id] = choice
for b in data.get("battles", []):
battle = Battle.fromDict(b)
rnd.battles[battle.sector_id] = battle
2026-01-21 08:31:48 +01:00
rnd.set_state(data.get("is_over", False))
2026-01-30 10:52:19 +01:00
return rnd
2026-02-02 10:41:16 +01:00
# Choice methods
2026-01-30 10:52:19 +01:00
2026-01-30 15:32:44 +01:00
def get_choice(self, participant_id: str) -> Choice | None:
2026-01-30 10:52:19 +01:00
return self.choices.get(participant_id)
2026-02-02 10:41:16 +01:00
def has_choice_with_sector(self, sector_id: str) -> bool:
return any(
choice.priority_sector_id == sector_id
or choice.secondary_sector_id == sector_id
for choice in self.choices.values()
)
def has_choice_with_participant(self, participant_id: str) -> bool:
return any(
choice.participant_id == participant_id for choice in self.choices.values()
)
2026-01-30 15:32:44 +01:00
def create_choice(self, participant_id: str) -> Choice:
if self.is_over:
# TODO catch me if you can
raise ForbiddenOperation("Can't create choice in a closed round.")
2026-01-30 15:32:44 +01:00
if participant_id not in self.choices:
choice = Choice(
2026-02-02 10:41:16 +01:00
participant_id=participant_id,
priority_sector_id=None,
secondary_sector_id=None,
2026-01-30 15:32:44 +01:00
)
self.choices[participant_id] = choice
return self.choices[participant_id]
2026-01-30 10:52:19 +01:00
2026-02-02 10:41:16 +01:00
def update_choice(
self,
participant_id: str,
priority_sector_id: str | None,
secondary_sector_id: str | None,
comment: str | None,
2026-02-04 16:10:53 +01:00
) -> None:
if self.is_over:
# TODO catch me if you can
raise ForbiddenOperation("Can't update choice in a closed round.")
# TODO prevent if battles already assigned
2026-01-30 18:55:39 +01:00
choice = self.get_choice(participant_id)
2026-02-04 16:10:53 +01:00
if choice:
choice.set_priority(priority_sector_id)
choice.set_secondary(secondary_sector_id)
choice.set_comment(comment)
2026-01-30 18:55:39 +01:00
# FIXME remove corresponding InfluenceSpent and TieResolved
def clear_sector_references(self, sector_id: str) -> None:
for choice in self.choices.values():
trigger_revert_ties = False
if choice.priority_sector_id == sector_id:
choice.priority_sector_id = None
trigger_revert_ties = True
if choice.secondary_sector_id == sector_id:
choice.secondary_sector_id = None
trigger_revert_ties = True
if trigger_revert_ties:
if self._campaign and self._campaign._war:
self._campaign._war.revert_choice_ties(self.id, sector_id=sector_id)
2026-02-04 16:10:53 +01:00
def remove_choice(self, participant_id: str) -> None:
if self.is_over:
# TODO catch me if you can (inner)
raise ForbiddenOperation("Can't remove choice in a closed round.")
# TODO prevent if battles already assigned
if self._campaign and self._campaign._war:
self._campaign._war.revert_choice_ties(
self.id, participants=[participant_id]
)
2026-01-30 15:32:44 +01:00
del self.choices[participant_id]
2026-02-02 10:41:16 +01:00
# Battle methods
2026-01-30 18:55:39 +01:00
def get_battle(self, sector_id: str) -> Battle | None:
return self.battles.get(sector_id)
2026-02-02 10:41:16 +01:00
def has_battle_with_sector(self, sector_id: str) -> bool:
return any(bat.sector_id == sector_id for bat in self.battles.values())
def has_battle_with_participant(self, participant_id: str) -> bool:
return any(
bat.player_1_id == participant_id or bat.player_2_id == participant_id
for bat in self.battles.values()
)
def has_finished_battle(self) -> bool:
return any(b.is_finished() for b in self.battles.values())
def all_battles_finished(self) -> bool:
return all(
b.winner_id is not None or b.is_draw() for b in self.battles.values()
)
2026-03-11 11:44:57 +01:00
def get_battles_with_places(self) -> List[Battle]:
return [
battle for battle in self.battles.values() if battle.get_available_places()
]
2026-01-30 18:55:39 +01:00
def create_battle(self, sector_id: str) -> Battle:
if self.is_over:
# TODO catch me if you can
raise ForbiddenOperation("Can't create battle in a closed round.")
2026-01-30 18:55:39 +01:00
if sector_id not in self.battles:
2026-02-02 10:41:16 +01:00
battle = Battle(sector_id=sector_id, player_1_id=None, player_2_id=None)
2026-01-30 18:55:39 +01:00
self.battles[sector_id] = battle
return self.battles[sector_id]
2026-02-02 10:41:16 +01:00
def update_battle(
self,
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,
2026-02-04 16:10:53 +01:00
) -> None:
if self.is_over:
# TODO catch me if you can
raise ForbiddenOperation("Can't update battle in a closed round.")
2026-01-30 18:55:39 +01:00
bat = self.get_battle(sector_id)
# TODO require confirmation if there was choice tie to clear it
2026-02-04 16:10:53 +01:00
if bat:
bat.set_player_1(player_1_id)
bat.set_player_2(player_2_id)
bat.set_winner(winner_id)
bat.set_score(score)
bat.set_victory_condition(victory_condition)
bat.set_comment(comment)
def clear_participant_references(self, participant_id: str) -> None:
for battle in self.battles.values():
trigger_revert_ties = False
if battle.player_1_id == participant_id:
battle.player_1_id = None
trigger_revert_ties = True
if battle.player_2_id == participant_id:
battle.player_2_id = None
trigger_revert_ties = True
if battle.winner_id == participant_id:
battle.winner_id = None
if trigger_revert_ties:
if self._campaign and self._campaign._war:
self._campaign._war.revert_battle_ties(battle.sector_id)
2026-02-04 16:10:53 +01:00
def remove_battle(self, sector_id: str) -> None:
if self.is_over:
# TODO catch me if you can
raise ForbiddenOperation("Can't remove battle in a closed round.")
bat = self.battles[sector_id]
if bat and bat.is_finished():
# TODO catch me if you can
raise ForbiddenOperation("Can't remove finished battle.")
if self._campaign and self._campaign._war:
self._campaign._war.revert_battle_ties(sector_id)
2026-01-30 18:55:39 +01:00
del self.battles[sector_id]