optimise global fallbcak score
This commit is contained in:
parent
2505573250
commit
c144845376
4 changed files with 85 additions and 23 deletions
|
|
@ -30,6 +30,7 @@ class Presenter:
|
|||
campaign: Campaign | None = None,
|
||||
round: Round | None = None,
|
||||
) -> TieDialogData:
|
||||
# TODO display Nth place
|
||||
if ctx.context_type == ContextType.WAR:
|
||||
if ctx.objective_id:
|
||||
obj = war.objectives[ctx.objective_id]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from typing import Dict, List, Tuple, TYPE_CHECKING
|
|||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from uuid import uuid4
|
||||
|
||||
from copy import deepcopy
|
||||
import random
|
||||
|
||||
from warchron.constants import ContextType, ScoreKind, ChoiceStatus, AllocationType
|
||||
|
|
@ -33,6 +33,14 @@ class AllocationResult:
|
|||
fallback: bool
|
||||
|
||||
|
||||
@dataclass
|
||||
class _FallbackState:
|
||||
assignments: List[Tuple[str, str]] # (pid, battle_id)
|
||||
remaining: List[str]
|
||||
occupancy: Dict[str, List[str]]
|
||||
score: int
|
||||
|
||||
|
||||
class Pairing:
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -325,36 +333,87 @@ class Pairing:
|
|||
if campaign is None:
|
||||
raise DomainError("Campaign not found")
|
||||
match_counts = Pairing.build_match_count(campaign)
|
||||
random.shuffle(remaining)
|
||||
for pid in list(remaining):
|
||||
available = round.get_battles_with_places()
|
||||
if not available:
|
||||
raise DomainError("No available battle remaining")
|
||||
scored: List[ScoredBattle] = [
|
||||
(
|
||||
Pairing._fallback_score(pid, battle, match_counts),
|
||||
battle,
|
||||
)
|
||||
for battle in available
|
||||
]
|
||||
scored.sort(key=lambda x: x[0])
|
||||
best_score = scored[0][0]
|
||||
best_battles = [b for s, b in scored if s == best_score]
|
||||
chosen = sorted(best_battles, key=lambda b: b.sector_id)[0]
|
||||
chosen.assign_participant(pid)
|
||||
available_battles = round.get_battles_with_places()
|
||||
if not available_battles:
|
||||
raise DomainError("No available battle remaining")
|
||||
occupancy = {
|
||||
b.sector_id: [p for p in (b.player_1_id, b.player_2_id) if p is not None]
|
||||
for b in round.battles.values()
|
||||
}
|
||||
shuffled_remaining = list(remaining)
|
||||
random.shuffle(shuffled_remaining)
|
||||
initial = _FallbackState(
|
||||
assignments=[],
|
||||
remaining=shuffled_remaining,
|
||||
occupancy=occupancy,
|
||||
score=0,
|
||||
)
|
||||
best = Pairing._search_best_fallback(
|
||||
initial,
|
||||
match_counts,
|
||||
beam_width=10,
|
||||
)
|
||||
battle_map = {b.sector_id: b for b in round.battles.values()}
|
||||
for pid, bid in best.assignments:
|
||||
battle_map[bid].assign_participant(pid)
|
||||
remaining.remove(pid)
|
||||
|
||||
@staticmethod
|
||||
def _fallback_score(
|
||||
def _search_best_fallback(
|
||||
initial: _FallbackState,
|
||||
match_counts: Dict[Tuple[str, str], int],
|
||||
beam_width: int,
|
||||
) -> _FallbackState:
|
||||
states = [initial]
|
||||
while True:
|
||||
new_states: List[_FallbackState] = []
|
||||
progress = False
|
||||
for state in states:
|
||||
if not state.remaining:
|
||||
new_states.append(state)
|
||||
continue
|
||||
progress = True
|
||||
pid = state.remaining[0]
|
||||
next_remaining = state.remaining[1:]
|
||||
battles = Pairing._available_virtual_battles(state)
|
||||
random.shuffle(battles)
|
||||
for sector_id in battles:
|
||||
occupants = state.occupancy[sector_id]
|
||||
if len(occupants) >= 2:
|
||||
continue
|
||||
score = Pairing._fallback_score_virtual(
|
||||
pid,
|
||||
occupants,
|
||||
match_counts,
|
||||
)
|
||||
new_occupancy = deepcopy(state.occupancy)
|
||||
new_occupancy[sector_id] = occupants + [pid]
|
||||
new_states.append(
|
||||
_FallbackState(
|
||||
assignments=state.assignments + [(pid, sector_id)],
|
||||
remaining=next_remaining,
|
||||
occupancy=new_occupancy,
|
||||
score=state.score + score,
|
||||
)
|
||||
)
|
||||
if not progress:
|
||||
break
|
||||
new_states.sort(key=lambda s: (s.score, random.random()))
|
||||
states = new_states[:beam_width]
|
||||
return min(states, key=lambda s: s.score)
|
||||
|
||||
@staticmethod
|
||||
def _available_virtual_battles(state: _FallbackState) -> List[str]:
|
||||
return [sector_id for sector_id, occ in state.occupancy.items() if len(occ) < 2]
|
||||
|
||||
@staticmethod
|
||||
def _fallback_score_virtual(
|
||||
pid: str,
|
||||
battle: Battle,
|
||||
occupants: List[str],
|
||||
match_counts: Dict[Tuple[str, str], int],
|
||||
) -> int:
|
||||
rematch_weight = 1
|
||||
occupancy_weight = 1
|
||||
occupants = [
|
||||
p for p in (battle.player_1_id, battle.player_2_id) if p is not None
|
||||
]
|
||||
occupancy_penalty = len(occupants)
|
||||
rematch_penalty = 0
|
||||
for opp in occupants:
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ class Round:
|
|||
return any(b.is_finished() for b in self.battles.values())
|
||||
|
||||
def all_battles_finished(self) -> bool:
|
||||
# TODO exception for participant alone
|
||||
return all(
|
||||
b.winner_id is not None or b.is_draw() for b in self.battles.values()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||
self.on_delete_item(ItemType.PLAYER, player_id)
|
||||
|
||||
def display_players(self, players: List[ParticipantOption]) -> None:
|
||||
# TODO display stats (war, campaign battles...)
|
||||
table = self.playersTable
|
||||
table.setSortingEnabled(False)
|
||||
table.setRowCount(len(players))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue