detect campaign tie

This commit is contained in:
Maxime Réaux 2026-02-20 11:01:25 +01:00
parent 7c9c941864
commit 60d8e6ca15
9 changed files with 203 additions and 116 deletions

View file

@ -1,17 +1,27 @@
from typing import List, Dict
from typing import List, Dict, DefaultDict
from dataclasses import dataclass
from collections import defaultdict
from warchron.constants import ContextType
from warchron.model.exception import ForbiddenOperation
from warchron.model.war import War
from warchron.model.round import Round
from warchron.model.battle import Battle
from warchron.model.war_event import InfluenceSpent, TieResolved
from warchron.model.score_service import ScoreService
@dataclass
class TieContext:
context_type: ContextType
context_id: str
participants: List[str] # war_participant_ids
class TieResolver:
@staticmethod
def find_round_ties(round: Round, war: War) -> List[Battle]:
def find_battle_ties(war: War, round_id: str) -> List[TieContext]:
round = war.get_round(round_id)
campaign = war.get_campaign_by_round(round_id)
ties = []
for battle in round.battles.values():
if not battle.is_draw():
@ -22,10 +32,53 @@ class TieResolver:
and e.context_id == battle.sector_id
for e in war.events
)
if not resolved:
ties.append(battle)
if resolved:
continue
if campaign is None:
raise RuntimeError("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
ties.append(
TieContext(
context_type=ContextType.BATTLE,
context_id=battle.sector_id,
participants=[p1, p2],
)
)
return ties
@staticmethod
def find_campaign_ties(war: War, campaign_id: str) -> List[TieContext]:
resolved = any(
isinstance(e, TieResolved)
and e.context_type == ContextType.CAMPAIGN
and e.context_id == campaign_id
for e in war.events
)
if resolved:
return []
scores = ScoreService.compute_scores(war, ContextType.CAMPAIGN, campaign_id)
buckets: DefaultDict[int, List[str]] = defaultdict(list)
for pid, score in scores.items():
buckets[score.victory_points].append(pid)
ties = []
for participants in buckets.values():
if len(participants) > 1:
ties.append(
TieContext(
context_type=ContextType.CAMPAIGN,
context_id=campaign_id,
participants=participants,
)
)
return ties
@staticmethod
def find_war_ties(war: War) -> List[TieContext]:
return [] # TODO
@staticmethod
def apply_bids(
war: War,
@ -88,25 +141,6 @@ class TieResolver:
def can_tie_be_resolved(war: War, participants: List[str]) -> bool:
return any(war.get_influence_tokens(pid) > 0 for pid in participants)
@staticmethod
def get_effective_winner_id(
war: War,
context_type: ContextType,
context_id: str,
base_winner_id: str | None,
) -> str | None:
if base_winner_id is not None:
return base_winner_id
for ev in reversed(war.events):
if (
isinstance(ev, TieResolved)
and ev.context_type == context_type
and ev.context_id == context_id
):
return ev.participant_id # None if confirmed draw
return None
@staticmethod
def was_tie_broken_by_tokens(
war: War,