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,
|
campaign: Campaign | None = None,
|
||||||
round: Round | None = None,
|
round: Round | None = None,
|
||||||
) -> TieDialogData:
|
) -> TieDialogData:
|
||||||
|
# TODO display Nth place
|
||||||
if ctx.context_type == ContextType.WAR:
|
if ctx.context_type == ContextType.WAR:
|
||||||
if ctx.objective_id:
|
if ctx.objective_id:
|
||||||
obj = war.objectives[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 collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
from copy import deepcopy
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from warchron.constants import ContextType, ScoreKind, ChoiceStatus, AllocationType
|
from warchron.constants import ContextType, ScoreKind, ChoiceStatus, AllocationType
|
||||||
|
|
@ -33,6 +33,14 @@ class AllocationResult:
|
||||||
fallback: bool
|
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:
|
class Pairing:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -325,36 +333,87 @@ class Pairing:
|
||||||
if campaign is None:
|
if campaign is None:
|
||||||
raise DomainError("Campaign not found")
|
raise DomainError("Campaign not found")
|
||||||
match_counts = Pairing.build_match_count(campaign)
|
match_counts = Pairing.build_match_count(campaign)
|
||||||
random.shuffle(remaining)
|
available_battles = round.get_battles_with_places()
|
||||||
for pid in list(remaining):
|
if not available_battles:
|
||||||
available = round.get_battles_with_places()
|
raise DomainError("No available battle remaining")
|
||||||
if not available:
|
occupancy = {
|
||||||
raise DomainError("No available battle remaining")
|
b.sector_id: [p for p in (b.player_1_id, b.player_2_id) if p is not None]
|
||||||
scored: List[ScoredBattle] = [
|
for b in round.battles.values()
|
||||||
(
|
}
|
||||||
Pairing._fallback_score(pid, battle, match_counts),
|
shuffled_remaining = list(remaining)
|
||||||
battle,
|
random.shuffle(shuffled_remaining)
|
||||||
)
|
initial = _FallbackState(
|
||||||
for battle in available
|
assignments=[],
|
||||||
]
|
remaining=shuffled_remaining,
|
||||||
scored.sort(key=lambda x: x[0])
|
occupancy=occupancy,
|
||||||
best_score = scored[0][0]
|
score=0,
|
||||||
best_battles = [b for s, b in scored if s == best_score]
|
)
|
||||||
chosen = sorted(best_battles, key=lambda b: b.sector_id)[0]
|
best = Pairing._search_best_fallback(
|
||||||
chosen.assign_participant(pid)
|
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)
|
remaining.remove(pid)
|
||||||
|
|
||||||
@staticmethod
|
@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,
|
pid: str,
|
||||||
battle: Battle,
|
occupants: List[str],
|
||||||
match_counts: Dict[Tuple[str, str], int],
|
match_counts: Dict[Tuple[str, str], int],
|
||||||
) -> int:
|
) -> int:
|
||||||
rematch_weight = 1
|
rematch_weight = 1
|
||||||
occupancy_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)
|
occupancy_penalty = len(occupants)
|
||||||
rematch_penalty = 0
|
rematch_penalty = 0
|
||||||
for opp in occupants:
|
for opp in occupants:
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,7 @@ class Round:
|
||||||
return any(b.is_finished() for b in self.battles.values())
|
return any(b.is_finished() for b in self.battles.values())
|
||||||
|
|
||||||
def all_battles_finished(self) -> bool:
|
def all_battles_finished(self) -> bool:
|
||||||
|
# TODO exception for participant alone
|
||||||
return all(
|
return all(
|
||||||
b.winner_id is not None or b.is_draw() for b in self.battles.values()
|
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)
|
self.on_delete_item(ItemType.PLAYER, player_id)
|
||||||
|
|
||||||
def display_players(self, players: List[ParticipantOption]) -> None:
|
def display_players(self, players: List[ParticipantOption]) -> None:
|
||||||
|
# TODO display stats (war, campaign battles...)
|
||||||
table = self.playersTable
|
table = self.playersTable
|
||||||
table.setSortingEnabled(False)
|
table.setSortingEnabled(False)
|
||||||
table.setRowCount(len(players))
|
table.setRowCount(len(players))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue