choice tie-break and random fallback + exception cleanup + war<->camp pid

This commit is contained in:
Maxime Réaux 2026-03-12 16:28:20 +01:00
parent 241d7f10f5
commit 241e76c937
15 changed files with 241 additions and 157 deletions

View file

@ -3,7 +3,7 @@ from dataclasses import dataclass, field
from collections import defaultdict
from warchron.constants import ContextType, ScoreKind
from warchron.model.exception import ForbiddenOperation
from warchron.model.exception import ForbiddenOperation, DomainError
from warchron.model.war import War
from warchron.model.war_event import InfluenceSpent, TieResolved
from warchron.model.score_service import ScoreService, ParticipantScore
@ -17,6 +17,7 @@ class TieContext:
score_value: int | None = None
score_kind: ScoreKind | None = None
objective_id: str | None = None
sector_id: str | None = None
def key(self) -> tuple[str, str, int | None]:
return (self.context_type, self.context_id, self.score_value)
@ -24,71 +25,6 @@ class TieContext:
class TieResolver:
@staticmethod
def find_choice_ties(
war: War,
round_id: str,
) -> List[TieContext]:
round = war.get_round(round_id)
campaign = war.get_campaign_by_round(round_id)
if campaign is None:
raise RuntimeError("Round without campaign")
ties: List[TieContext] = []
scores = ScoreService.compute_scores(
war,
ContextType.CAMPAIGN,
campaign.id,
)
score_groups = ScoreService.group_participants_by_score(
scores, lambda score: score.victory_points
)
sector_to_battle = {b.sector_id: b for b in round.battles.values()}
for group in score_groups:
if len(group) <= 1:
continue
demand: Dict[str, List[str]] = {}
for pid in group:
choice = round.choices.get(pid)
if not choice:
continue
for sec_id in (
choice.priority_sector_id,
choice.secondary_sector_id,
):
if sec_id:
demand.setdefault(sec_id, []).append(pid)
for sector_id, demanders in demand.items():
battle = sector_to_battle.get(sector_id)
if battle is None:
continue
places = len(battle.get_available_places())
if len(demanders) <= places:
continue
context = TieContext(
ContextType.CHOICE,
round_id,
demanders,
score_value=None,
score_kind=ScoreKind.VP,
)
if TieResolver.is_tie_resolved(war, context):
continue
if not TieResolver.can_tie_be_resolved(
war,
context,
demanders,
):
war.events.append(
TieResolved(
None,
ContextType.CHOICE,
round_id,
)
)
continue
ties.append(context)
return ties
@staticmethod
def find_battle_ties(war: War, round_id: str) -> List[TieContext]:
round = war.get_round(round_id)
@ -104,12 +40,12 @@ class TieResolver:
if TieResolver.is_tie_resolved(war, context):
continue
if campaign is None:
raise RuntimeError("No campaign for this battle tie")
raise DomainError("No campaign for this battle tie")
if battle.player_1_id is None or battle.player_2_id is None:
raise RuntimeError("Missing player(s) in this battle context.")
p1 = campaign.participants[battle.player_1_id].war_participant_id
p2 = campaign.participants[battle.player_2_id].war_participant_id
if not TieResolver.can_tie_be_resolved(war, context, [p1, p2]):
raise DomainError("Missing player(s) in this battle context.")
p1_id = campaign.campaign_to_war_part_id(battle.player_1_id)
p2_id = campaign.campaign_to_war_part_id(battle.player_2_id)
if not TieResolver.can_tie_be_resolved(war, context, [p1_id, p2_id]):
war.events.append(
TieResolved(None, ContextType.BATTLE, battle.sector_id)
)
@ -118,7 +54,7 @@ class TieResolver:
TieContext(
context_type=ContextType.BATTLE,
context_id=battle.sector_id,
participants=[p1, p2],
participants=[p1_id, p2_id],
score_value=None,
score_kind=None,
)
@ -240,8 +176,8 @@ class TieResolver:
ContextType.WAR,
war.id,
[],
score_value,
ScoreKind.VP,
score_value=score_value,
score_kind=ScoreKind.VP,
)
if TieResolver.is_tie_resolved(war, context):
continue
@ -427,15 +363,15 @@ class TieResolver:
context,
context.participants,
)
# confirmed draw if non had bid
# confirmed draw if none had bid
if not active:
war.events.append(
TieResolved(
None,
context.context_type,
context.context_id,
context.score_value,
context.objective_id,
score_value=context.score_value,
objective_id=context.objective_id,
)
)
return
@ -446,8 +382,8 @@ class TieResolver:
None,
context.context_type,
context.context_id,
context.score_value,
context.objective_id,
score_value=context.score_value,
objective_id=context.objective_id,
)
)
return