fix tie resolve group order + battle draw token icon

This commit is contained in:
Maxime Réaux 2026-03-20 10:37:35 +01:00
parent 719b0128ed
commit b7a35f6712
5 changed files with 129 additions and 75 deletions

View file

@ -1,6 +1,7 @@
from typing import List, Dict, DefaultDict, Tuple
from __future__ import annotations
from typing import List, Dict, Tuple, Callable, TypeAlias
from dataclasses import dataclass
from collections import defaultdict
from uuid import uuid4
from warchron.constants import ContextType, ScoreKind
from warchron.model.exception import ForbiddenOperation, DomainError
@ -29,6 +30,12 @@ class TieContext:
)
ResolveTiesCallback: TypeAlias = Callable[
[War, List[TieContext]],
Dict[Tuple[str, str, int | None, str | None, str | None], Dict[str, bool]],
]
class TieBreaker:
@staticmethod
@ -92,14 +99,22 @@ class TieBreaker:
@staticmethod
def find_campaign_ties(war: War, campaign_id: str) -> List[TieContext]:
from warchron.model.checking import ResultChecker
scores = ScoreComputer.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)
ranking = ResultChecker.get_effective_ranking(
war,
ContextType.CAMPAIGN,
campaign_id,
ScoreKind.VP,
scores,
lambda s: s.victory_points,
)
ties: List[TieContext] = []
for score_value, participants in buckets.items():
if len(participants) <= 1:
for _, group, _ in ranking:
if len(group) <= 1:
continue
score_value = scores[group[0]].victory_points
context: TieContext = TieContext(
ContextType.CAMPAIGN,
campaign_id,
@ -110,13 +125,13 @@ class TieBreaker:
if TieBreaker.is_tie_resolved(war, context):
continue
tie_id = TieBreaker.find_active_tie_id(war, context)
if not TieBreaker.can_tie_be_resolved(war, context, participants):
if not TieBreaker.can_tie_be_resolved(war, context, group):
war.events.append(
TieResolved(
participant_id=None,
context_type=ContextType.CAMPAIGN,
context_id=campaign_id,
participants=participants,
participants=group,
tie_id=tie_id,
score_value=score_value,
)
@ -126,7 +141,7 @@ class TieBreaker:
TieContext(
context_type=ContextType.CAMPAIGN,
context_id=campaign_id,
participants=participants,
participants=group,
score_value=score_value,
score_kind=ScoreKind.VP,
)
@ -137,20 +152,32 @@ class TieBreaker:
def find_campaign_objective_ties(
war: War, campaign_id: str, objective_id: str
) -> List[TieContext]:
from warchron.model.checking import ResultChecker
scores = ScoreComputer.compute_scores(
war,
ContextType.CAMPAIGN,
campaign_id,
)
buckets: DefaultDict[int, List[str]] = defaultdict(list)
for pid, score in scores.items():
np_value = score.narrative_points.get(objective_id, 0)
buckets[np_value].append(pid)
def value_getter(score: ParticipantScore) -> int:
return score.narrative_points.get(objective_id, 0)
ranking = ResultChecker.get_effective_ranking(
war,
ContextType.CAMPAIGN,
campaign_id,
ScoreKind.NP,
scores,
value_getter,
objective_id,
)
ties: List[TieContext] = []
context_id = campaign_id
for np_value, participants in buckets.items():
if len(participants) <= 1:
for _, group, _ in ranking:
if len(group) <= 1:
continue
np_value = value_getter(scores[group[0]])
context: TieContext = TieContext(
ContextType.CAMPAIGN,
campaign_id,
@ -165,14 +192,14 @@ class TieBreaker:
if not TieBreaker.can_tie_be_resolved(
war,
context,
participants,
group,
):
war.events.append(
TieResolved(
participant_id=None,
context_type=ContextType.CAMPAIGN,
context_id=context_id,
participants=participants,
participants=group,
tie_id=tie_id,
score_value=np_value,
objective_id=objective_id,
@ -183,7 +210,7 @@ class TieBreaker:
TieContext(
context_type=ContextType.CAMPAIGN,
context_id=context_id,
participants=participants,
participants=group,
score_value=np_value,
score_kind=ScoreKind.NP,
objective_id=objective_id,
@ -307,6 +334,51 @@ class TieBreaker:
)
return ties
@staticmethod
def resolve_group(
war: War,
context: TieContext,
resolve_ties_callback: ResolveTiesCallback,
) -> None:
tie_id = TieBreaker.find_active_tie_id(war, context) or str(uuid4())
while not TieBreaker.is_tie_resolved(war, context):
active = TieBreaker.get_active_participants(
war,
context,
context.participants,
)
current_context = TieContext(
context_type=context.context_type,
context_id=context.context_id,
participants=active,
score_value=context.score_value,
score_kind=context.score_kind,
objective_id=context.objective_id,
sector_id=context.sector_id,
)
if not TieBreaker.can_tie_be_resolved(
war,
context,
current_context.participants,
):
war.events.append(
TieResolved(
None,
context.context_type,
context.context_id,
participants=context.participants,
tie_id=tie_id,
score_value=context.score_value,
objective_id=context.objective_id,
sector_id=context.sector_id,
)
)
return
bids_map = resolve_ties_callback(war, [current_context])
bids = bids_map[current_context.key()]
TieBreaker.apply_bids(war, context, tie_id, bids)
TieBreaker.resolve_tie_state(war, context, tie_id, bids)
@staticmethod
def apply_bids(
war: War,
@ -447,22 +519,6 @@ class TieBreaker:
active = TieBreaker.get_active_participants(war, context, participants)
return any(war.get_influence_tokens(pid) > 0 for pid in active)
@staticmethod
def was_tie_broken_by_tokens(
war: War,
context: TieContext,
) -> bool:
for ev in reversed(war.events):
if (
isinstance(ev, TieResolved)
and ev.context_type == context.context_type
and ev.context_id == context.context_id
and ev.score_value == context.score_value
and ev.objective_id == context.objective_id
):
return ev.participant_id is not None
return False
@staticmethod
def is_tie_resolved(war: War, context: TieContext) -> bool:
for ev in war.events: