wip close round/campaign/war + refacto json None
This commit is contained in:
parent
4c8086caf4
commit
6cbb7c6534
26 changed files with 474 additions and 108 deletions
|
|
@ -40,26 +40,30 @@ class Battle:
|
|||
def set_comment(self, new_comment: str | None) -> None:
|
||||
self.comment = new_comment
|
||||
|
||||
# TODO improve draw detection
|
||||
def is_draw(self) -> bool:
|
||||
return self.winner_id is None and self.score is not None
|
||||
|
||||
def toDict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"sector_id": self.sector_id,
|
||||
"player_1_id": self.player_1_id,
|
||||
"player_2_id": self.player_2_id,
|
||||
"winner_id": self.winner_id,
|
||||
"score": self.score,
|
||||
"victory_condition": self.victory_condition,
|
||||
"comment": self.comment,
|
||||
"player_1_id": self.player_1_id or None,
|
||||
"player_2_id": self.player_2_id or None,
|
||||
"winner_id": self.winner_id or None,
|
||||
"score": self.score or None,
|
||||
"victory_condition": self.victory_condition or None,
|
||||
"comment": self.comment or None,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def fromDict(data: Dict[str, Any]) -> Battle:
|
||||
battle = Battle(
|
||||
data["sector_id"],
|
||||
data.get("player_1_id"),
|
||||
data.get("player_2_id"),
|
||||
data.get("player_1_id") or None,
|
||||
data.get("player_2_id") or None,
|
||||
)
|
||||
battle.winner_id = data.get("winner_id")
|
||||
battle.score = data.get("score")
|
||||
battle.victory_condition = data.get("victory_condition")
|
||||
battle.comment = data.get("comment")
|
||||
battle.winner_id = data.get("winner_id") or None
|
||||
battle.score = data.get("score") or None
|
||||
battle.victory_condition = data.get("victory_condition") or None
|
||||
battle.comment = data.get("comment") or None
|
||||
return battle
|
||||
|
|
|
|||
|
|
@ -146,7 +146,12 @@ class Campaign:
|
|||
)
|
||||
|
||||
def add_sector(
|
||||
self, name: str, round_id: str, major_id: str, minor_id: str, influence_id: str
|
||||
self,
|
||||
name: str,
|
||||
round_id: str | None,
|
||||
major_id: str | None,
|
||||
minor_id: str | None,
|
||||
influence_id: str | None,
|
||||
) -> Sector:
|
||||
sect = Sector(name, round_id, major_id, minor_id, influence_id)
|
||||
self.sectors[sect.id] = sect
|
||||
|
|
@ -168,10 +173,10 @@ class Campaign:
|
|||
sector_id: str,
|
||||
*,
|
||||
name: str,
|
||||
round_id: str,
|
||||
major_id: str,
|
||||
minor_id: str,
|
||||
influence_id: str,
|
||||
round_id: str | None,
|
||||
major_id: str | None,
|
||||
minor_id: str | None,
|
||||
influence_id: str | None,
|
||||
) -> None:
|
||||
sect = self.get_sector(sector_id)
|
||||
old_round_id = sect.round_id
|
||||
|
|
@ -308,6 +313,9 @@ class Campaign:
|
|||
|
||||
# Battle methods
|
||||
|
||||
def all_rounds_finished(self) -> bool:
|
||||
return all(r.is_over for r in self.rounds)
|
||||
|
||||
def create_battle(self, round_id: str, sector_id: str) -> Battle:
|
||||
rnd = self.get_round(round_id)
|
||||
return rnd.create_battle(sector_id)
|
||||
|
|
|
|||
|
|
@ -33,17 +33,17 @@ class Choice:
|
|||
def toDict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"participant_id": self.participant_id,
|
||||
"priority_sector_id": self.priority_sector_id,
|
||||
"secondary_sector_id": self.secondary_sector_id,
|
||||
"comment": self.comment,
|
||||
"priority_sector_id": self.priority_sector_id or None,
|
||||
"secondary_sector_id": self.secondary_sector_id or None,
|
||||
"comment": self.comment or None,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def fromDict(data: Dict[str, Any]) -> Choice:
|
||||
choice = Choice(
|
||||
data["participant_id"],
|
||||
data.get("priority_sector_id"),
|
||||
data.get("secondary_sector_id"),
|
||||
data.get("priority_sector_id") or None,
|
||||
data.get("secondary_sector_id") or None,
|
||||
)
|
||||
choice.comment = data.get("comment")
|
||||
choice.comment = data.get("comment") or None
|
||||
return choice
|
||||
|
|
|
|||
83
src/warchron/model/closure_service.py
Normal file
83
src/warchron/model/closure_service.py
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
from __future__ import annotations
|
||||
from typing import List
|
||||
|
||||
from warchron.constants import ContextType
|
||||
from warchron.model.tie_manager import ResolutionContext
|
||||
from warchron.model.war import War
|
||||
from warchron.model.campaign import Campaign
|
||||
from warchron.model.round import Round
|
||||
|
||||
|
||||
class ClosureService:
|
||||
|
||||
@staticmethod
|
||||
def close_round(round: Round) -> List[ResolutionContext]:
|
||||
if not round.all_battles_finished():
|
||||
raise RuntimeError("All battles must be finished to close their round")
|
||||
ties = []
|
||||
for battle in round.battles.values():
|
||||
if battle.is_draw():
|
||||
participants: list[str] = []
|
||||
if battle.player_1_id is not None:
|
||||
participants.append(battle.player_1_id)
|
||||
if battle.player_2_id is not None:
|
||||
participants.append(battle.player_2_id)
|
||||
ties.append(
|
||||
ResolutionContext(
|
||||
context_type=ContextType.BATTLE,
|
||||
context_id=battle.sector_id,
|
||||
# TODO ref to War.participants at some point
|
||||
participant_ids=participants,
|
||||
)
|
||||
)
|
||||
if ties:
|
||||
return ties
|
||||
round.is_over = True
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def close_campaign(campaign: Campaign) -> List[ResolutionContext]:
|
||||
if not campaign.all_rounds_finished():
|
||||
raise RuntimeError("All rounds must be finished to close their campaign")
|
||||
ties: List[ResolutionContext] = []
|
||||
# for round in campaign.rounds:
|
||||
# # compute score
|
||||
# # if participants have same score
|
||||
# ties.append(
|
||||
# ResolutionContext(
|
||||
# context_type=ContextType.CAMPAIGN,
|
||||
# context_id=campaign.id,
|
||||
# participant_ids=[
|
||||
# # TODO ref to War.participants at some point
|
||||
# campaign.participants[campaign_participant_id],
|
||||
# campaign.participants[campaign_participant_id],
|
||||
# ],
|
||||
# )
|
||||
# )
|
||||
if ties:
|
||||
return ties
|
||||
campaign.is_over = True
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def close_war(war: War) -> List[ResolutionContext]:
|
||||
if not war.all_campaigns_finished():
|
||||
raise RuntimeError("All campaigns must be finished to close their war")
|
||||
ties: List[ResolutionContext] = []
|
||||
# for campaign in war.campaigns:
|
||||
# # compute score
|
||||
# # if participants have same score
|
||||
# ties.append(
|
||||
# ResolutionContext(
|
||||
# context_type=ContextType.WAR,
|
||||
# context_id=war.id,
|
||||
# participant_ids=[
|
||||
# war.participants[war_participant_id],
|
||||
# war.participants[war_participant_id],
|
||||
# ],
|
||||
# )
|
||||
# )
|
||||
if ties:
|
||||
return ties
|
||||
war.is_over = True
|
||||
return []
|
||||
23
src/warchron/model/influence_service.py
Normal file
23
src/warchron/model/influence_service.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from warchron.model.war import War
|
||||
from warchron.model.campaign import Campaign
|
||||
from warchron.model.battle import Battle
|
||||
from warchron.model.war_event import InfluenceGained
|
||||
|
||||
|
||||
class InfluenceService:
|
||||
|
||||
@staticmethod
|
||||
def apply_battle_result(war: War, campaign: Campaign, battle: Battle) -> None:
|
||||
if battle.winner_id is None:
|
||||
return
|
||||
sector = campaign.sectors[battle.sector_id]
|
||||
# if sector grants influence
|
||||
if sector.influence_objective_id and war.influence_token:
|
||||
participant = war.participants[battle.winner_id]
|
||||
participant.events.append(
|
||||
InfluenceGained(
|
||||
participant_id=participant.id,
|
||||
amount=1,
|
||||
source=f"battle:{battle.sector_id}",
|
||||
)
|
||||
)
|
||||
|
|
@ -182,7 +182,9 @@ class Model:
|
|||
|
||||
# Objective methods
|
||||
|
||||
def add_objective(self, war_id: str, name: str, description: str) -> Objective:
|
||||
def add_objective(
|
||||
self, war_id: str, name: str, description: str | None
|
||||
) -> Objective:
|
||||
war = self.get_war(war_id)
|
||||
return war.add_objective(name, description)
|
||||
|
||||
|
|
@ -195,7 +197,7 @@ class Model:
|
|||
raise KeyError("Objective not found")
|
||||
|
||||
def update_objective(
|
||||
self, objective_id: str, *, name: str, description: str
|
||||
self, objective_id: str, *, name: str, description: str | None
|
||||
) -> None:
|
||||
war = self.get_war_by_objective(objective_id)
|
||||
war.update_objective(objective_id, name=name, description=description)
|
||||
|
|
@ -229,6 +231,11 @@ class Model:
|
|||
def get_player_from_war_participant(self, war_part: WarParticipant) -> Player:
|
||||
return self.get_player(war_part.player_id)
|
||||
|
||||
def get_participant_name(self, participant_id: str) -> str:
|
||||
war = self.get_war_by_war_participant(participant_id)
|
||||
war_part = war.get_war_participant(participant_id)
|
||||
return self.players[war_part.player_id].name
|
||||
|
||||
def update_war_participant(self, participant_id: str, *, faction: str) -> None:
|
||||
war = self.get_war_by_war_participant(participant_id)
|
||||
war.update_war_participant(participant_id, faction=faction)
|
||||
|
|
@ -290,10 +297,10 @@ class Model:
|
|||
self,
|
||||
campaign_id: str,
|
||||
name: str,
|
||||
round_id: str,
|
||||
major_id: str,
|
||||
minor_id: str,
|
||||
influence_id: str,
|
||||
round_id: str | None,
|
||||
major_id: str | None,
|
||||
minor_id: str | None,
|
||||
influence_id: str | None,
|
||||
) -> Sector:
|
||||
camp = self.get_campaign(campaign_id)
|
||||
return camp.add_sector(name, round_id, major_id, minor_id, influence_id)
|
||||
|
|
@ -312,10 +319,10 @@ class Model:
|
|||
sector_id: str,
|
||||
*,
|
||||
name: str,
|
||||
round_id: str,
|
||||
major_id: str,
|
||||
minor_id: str,
|
||||
influence_id: str,
|
||||
round_id: str | None,
|
||||
major_id: str | None,
|
||||
minor_id: str | None,
|
||||
influence_id: str | None,
|
||||
) -> None:
|
||||
war = self.get_war_by_sector(sector_id)
|
||||
war.update_sector(
|
||||
|
|
@ -343,11 +350,6 @@ class Model:
|
|||
camp = self.get_campaign(camp_id)
|
||||
return camp.add_campaign_participant(player_id, leader, theme)
|
||||
|
||||
def get_participant_name(self, participant_id: str) -> str:
|
||||
war = self.get_war_by_war_participant(participant_id)
|
||||
war_part = war.get_war_participant(participant_id)
|
||||
return self.players[war_part.player_id].name
|
||||
|
||||
# TODO replace multiloops by internal has_* method
|
||||
def get_campaign_participant(self, participant_id: str) -> CampaignParticipant:
|
||||
for war in self.wars.values():
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ from uuid import uuid4
|
|||
|
||||
|
||||
class Objective:
|
||||
def __init__(self, name: str, description: str):
|
||||
def __init__(self, name: str, description: str | None):
|
||||
self.id: str = str(uuid4())
|
||||
self.name: str = name
|
||||
self.description: str = description
|
||||
self.description: str | None = description
|
||||
|
||||
def set_id(self, new_id: str) -> None:
|
||||
self.id = new_id
|
||||
|
|
@ -15,18 +15,18 @@ class Objective:
|
|||
def set_name(self, new_name: str) -> None:
|
||||
self.name = new_name
|
||||
|
||||
def set_description(self, new_description: str) -> None:
|
||||
def set_description(self, new_description: str | None) -> None:
|
||||
self.description = new_description
|
||||
|
||||
def toDict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"description": self.description or None,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def fromDict(data: Dict[str, Any]) -> Objective:
|
||||
obj = Objective(data["name"], data["description"])
|
||||
obj = Objective(data["name"], data["description"] or None)
|
||||
obj.set_id(data["id"])
|
||||
return obj
|
||||
|
|
|
|||
|
|
@ -104,6 +104,11 @@ class Round:
|
|||
for bat 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()
|
||||
)
|
||||
|
||||
def create_battle(self, sector_id: str) -> Battle:
|
||||
if sector_id not in self.battles:
|
||||
battle = Battle(sector_id=sector_id, player_1_id=None, player_2_id=None)
|
||||
|
|
|
|||
39
src/warchron/model/score_service.py
Normal file
39
src/warchron/model/score_service.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from warchron.model.war import War
|
||||
|
||||
|
||||
class ScoreService:
|
||||
|
||||
@staticmethod
|
||||
def compute_victory_points_for_participant(war: "War", participant_id: str) -> int:
|
||||
total = 0
|
||||
for campaign in war.campaigns:
|
||||
for round_ in campaign.rounds:
|
||||
for battle in round_.battles.values():
|
||||
if battle.winner_id == participant_id:
|
||||
sector = campaign.sectors[battle.sector_id]
|
||||
if sector.major_objective_id:
|
||||
total += war.major_value
|
||||
if sector.minor_objective_id:
|
||||
total += war.minor_value
|
||||
return total
|
||||
|
||||
@staticmethod
|
||||
def compute_narrative_points_for_participant(
|
||||
war: "War", participant_id: str
|
||||
) -> dict[str, int]:
|
||||
totals: dict[str, int] = {}
|
||||
for obj_id in war.objectives:
|
||||
totals[obj_id] = 0
|
||||
for campaign in war.campaigns:
|
||||
for round_ in campaign.rounds:
|
||||
for battle in round_.battles.values():
|
||||
if battle.winner_id == participant_id:
|
||||
sector = campaign.sectors[battle.sector_id]
|
||||
if sector.major_objective_id:
|
||||
totals[sector.major_objective_id] += war.major_value
|
||||
if sector.minor_objective_id:
|
||||
totals[sector.minor_objective_id] += war.minor_value
|
||||
return totals
|
||||
|
|
@ -14,7 +14,7 @@ class Sector:
|
|||
):
|
||||
self.id: str = str(uuid4())
|
||||
self.name: str = name
|
||||
self.round_id: str | None = round_id
|
||||
self.round_id: str | None = round_id # ref to Campaign.rounds
|
||||
self.major_objective_id: str | None = major_id # ref to War.objectives
|
||||
self.minor_objective_id: str | None = minor_id # ref to War.objectives
|
||||
self.influence_objective_id: str | None = influence_id # ref to War.objectives
|
||||
|
|
@ -27,16 +27,16 @@ class Sector:
|
|||
def set_name(self, new_name: str) -> None:
|
||||
self.name = new_name
|
||||
|
||||
def set_round(self, new_round_id: str) -> None:
|
||||
def set_round(self, new_round_id: str | None) -> None:
|
||||
self.round_id = new_round_id
|
||||
|
||||
def set_major(self, new_major_id: str) -> None:
|
||||
def set_major(self, new_major_id: str | None) -> None:
|
||||
self.major_objective_id = new_major_id
|
||||
|
||||
def set_minor(self, new_minor_id: str) -> None:
|
||||
def set_minor(self, new_minor_id: str | None) -> None:
|
||||
self.minor_objective_id = new_minor_id
|
||||
|
||||
def set_influence(self, new_influence_id: str) -> None:
|
||||
def set_influence(self, new_influence_id: str | None) -> None:
|
||||
self.influence_objective_id = new_influence_id
|
||||
|
||||
def toDict(self) -> Dict[str, Any]:
|
||||
|
|
@ -44,11 +44,11 @@ class Sector:
|
|||
"id": self.id,
|
||||
"name": self.name,
|
||||
"round_id": self.round_id,
|
||||
"major_objective_id": self.major_objective_id,
|
||||
"minor_objective_id": self.minor_objective_id,
|
||||
"influence_objective_id": self.influence_objective_id,
|
||||
"mission": self.mission,
|
||||
"description": self.description,
|
||||
"major_objective_id": self.major_objective_id or None,
|
||||
"minor_objective_id": self.minor_objective_id or None,
|
||||
"influence_objective_id": self.influence_objective_id or None,
|
||||
"mission": self.mission or None,
|
||||
"description": self.description or None,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -56,11 +56,11 @@ class Sector:
|
|||
sec = Sector(
|
||||
data["name"],
|
||||
data["round_id"],
|
||||
data.get("major_objective_id"),
|
||||
data.get("minor_objective_id"),
|
||||
data.get("influence_objective_id"),
|
||||
data.get("major_objective_id") or None,
|
||||
data.get("minor_objective_id") or None,
|
||||
data.get("influence_objective_id") or None,
|
||||
)
|
||||
sec.set_id(data["id"])
|
||||
sec.mission = data.get("mission")
|
||||
sec.description = data.get("description")
|
||||
sec.mission = data.get("mission") or None
|
||||
sec.description = data.get("description") or None
|
||||
return sec
|
||||
|
|
|
|||
46
src/warchron/model/tie_manager.py
Normal file
46
src/warchron/model/tie_manager.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
from warchron.model.war import War
|
||||
from warchron.model.war_event import InfluenceSpent
|
||||
|
||||
|
||||
class ResolutionContext:
|
||||
def __init__(self, context_type: str, context_id: str, participant_ids: list[str]):
|
||||
self.context_type = context_type
|
||||
self.context_id = context_id
|
||||
self.participant_ids = participant_ids
|
||||
|
||||
self.current_bids: dict[str, int] = {}
|
||||
self.round_index: int = 0
|
||||
self.is_resolved: bool = False
|
||||
|
||||
|
||||
class TieResolver:
|
||||
|
||||
@staticmethod
|
||||
def resolve(
|
||||
war: War,
|
||||
context: ResolutionContext,
|
||||
bids: dict[str, int],
|
||||
) -> str | None:
|
||||
# verify available token for each player
|
||||
for pid, amount in bids.items():
|
||||
participant = war.participants[pid]
|
||||
if participant.influence_tokens() < amount:
|
||||
raise ValueError("Not enough influence tokens")
|
||||
# apply spending
|
||||
for pid, amount in bids.items():
|
||||
if amount > 0:
|
||||
war.participants[pid].events.append(
|
||||
InfluenceSpent(
|
||||
participant_id=pid,
|
||||
amount=amount,
|
||||
context=context.context_type,
|
||||
)
|
||||
)
|
||||
# determine winner
|
||||
max_bid = max(bids.values())
|
||||
winners = [pid for pid, b in bids.items() if b == max_bid]
|
||||
if len(winners) == 1:
|
||||
context.is_resolved = True
|
||||
return winners[0]
|
||||
# persisting tie → None
|
||||
return None
|
||||
|
|
@ -86,7 +86,7 @@ class War:
|
|||
|
||||
# Objective methods
|
||||
|
||||
def add_objective(self, name: str, description: str) -> Objective:
|
||||
def add_objective(self, name: str, description: str | None) -> Objective:
|
||||
obj = Objective(name, description)
|
||||
self.objectives[obj.id] = obj
|
||||
return obj
|
||||
|
|
@ -104,7 +104,7 @@ class War:
|
|||
return obj.name if obj else ""
|
||||
|
||||
def update_objective(
|
||||
self, objective_id: str, *, name: str, description: str
|
||||
self, objective_id: str, *, name: str, description: str | None
|
||||
) -> None:
|
||||
obj = self.get_objective(objective_id)
|
||||
obj.set_name(name)
|
||||
|
|
@ -173,6 +173,9 @@ class War:
|
|||
def get_default_campaign_values(self) -> Dict[str, Any]:
|
||||
return {"month": datetime.now().month}
|
||||
|
||||
def all_campaigns_finished(self) -> bool:
|
||||
return all(c.is_over for c in self.campaigns)
|
||||
|
||||
def add_campaign(self, name: str, month: int | None = None) -> Campaign:
|
||||
if month is None:
|
||||
month = self.get_default_campaign_values()["month"]
|
||||
|
|
@ -247,10 +250,10 @@ class War:
|
|||
sector_id: str,
|
||||
*,
|
||||
name: str,
|
||||
round_id: str,
|
||||
major_id: str,
|
||||
minor_id: str,
|
||||
influence_id: str,
|
||||
round_id: str | None,
|
||||
major_id: str | None,
|
||||
minor_id: str | None,
|
||||
influence_id: str | None,
|
||||
) -> None:
|
||||
camp = self.get_campaign_by_sector(sector_id)
|
||||
camp.update_sector(
|
||||
|
|
|
|||
30
src/warchron/model/war_event.py
Normal file
30
src/warchron/model/war_event.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
class WarEvent:
|
||||
def __init__(self, participant_id: str):
|
||||
self.id: str = str(uuid4())
|
||||
self.participant_id: str = participant_id
|
||||
self.timestamp: datetime = datetime.now()
|
||||
|
||||
|
||||
class TieResolved(WarEvent):
|
||||
def __init__(self, participant_id: str, context_type: str, context_id: str):
|
||||
super().__init__(participant_id)
|
||||
self.context_type = context_type # battle, round, campaign, war
|
||||
self.context_id = context_id
|
||||
|
||||
|
||||
class InfluenceGained(WarEvent):
|
||||
def __init__(self, participant_id: str, amount: int, source: str):
|
||||
super().__init__(participant_id)
|
||||
self.amount = amount
|
||||
self.source = source # "battle", "tie_resolution", etc.
|
||||
|
||||
|
||||
class InfluenceSpent(WarEvent):
|
||||
def __init__(self, participant_id: str, amount: int, context: str):
|
||||
super().__init__(participant_id)
|
||||
self.amount = amount
|
||||
self.context = context # "battle_tie", "campaign_tie", etc.
|
||||
|
|
@ -1,6 +1,12 @@
|
|||
from __future__ import annotations
|
||||
from typing import Any, Dict
|
||||
from uuid import uuid4
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from warchron.model.war import War
|
||||
from warchron.model.war_event import WarEvent, InfluenceSpent, InfluenceGained
|
||||
from warchron.model.score_service import ScoreService
|
||||
|
||||
|
||||
class WarParticipant:
|
||||
|
|
@ -8,6 +14,7 @@ class WarParticipant:
|
|||
self.id: str = str(uuid4())
|
||||
self.player_id: str = player_id # ref to Model.players
|
||||
self.faction: str = faction
|
||||
self.events: list[WarEvent] = []
|
||||
|
||||
def set_id(self, new_id: str) -> None:
|
||||
self.id = new_id
|
||||
|
|
@ -33,3 +40,16 @@ class WarParticipant:
|
|||
)
|
||||
part.set_id(data["id"])
|
||||
return part
|
||||
|
||||
# Computed properties
|
||||
|
||||
def influence_tokens(self) -> int:
|
||||
gained = sum(e.amount for e in self.events if isinstance(e, InfluenceGained))
|
||||
spent = sum(e.amount for e in self.events if isinstance(e, InfluenceSpent))
|
||||
return gained - spent
|
||||
|
||||
def victory_points(self, war: "War") -> int:
|
||||
return ScoreService.compute_victory_points_for_participant(war, self.id)
|
||||
|
||||
def narrative_points(self, war: "War") -> Dict[str, int]:
|
||||
return ScoreService.compute_narrative_points_for_participant(war, self.id)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue