allow closing round with incomplete battles
This commit is contained in:
parent
ae6c033bbe
commit
69942a3cff
10 changed files with 67 additions and 29 deletions
|
|
@ -235,7 +235,8 @@ class AppController:
|
||||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
)
|
)
|
||||||
if reply == QMessageBox.StandardButton.Yes:
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
e.action()
|
if e.action:
|
||||||
|
e.action()
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
self.is_dirty = True
|
self.is_dirty = True
|
||||||
|
|
@ -290,7 +291,8 @@ class AppController:
|
||||||
)
|
)
|
||||||
if reply == QMessageBox.StandardButton.Yes:
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
try:
|
try:
|
||||||
e.action()
|
if e.action:
|
||||||
|
e.action()
|
||||||
except DomainError as inner:
|
except DomainError as inner:
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self.view,
|
self.view,
|
||||||
|
|
@ -361,7 +363,8 @@ class AppController:
|
||||||
)
|
)
|
||||||
if reply == QMessageBox.StandardButton.Yes:
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
try:
|
try:
|
||||||
e.action()
|
if e.action:
|
||||||
|
e.action()
|
||||||
except DomainError as inner:
|
except DomainError as inner:
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self.view,
|
self.view,
|
||||||
|
|
|
||||||
|
|
@ -186,14 +186,36 @@ class RoundController:
|
||||||
camp = self.app.model.get_campaign_by_round(round_id)
|
camp = self.app.model.get_campaign_by_round(round_id)
|
||||||
war = self.app.model.get_war_by_round(round_id)
|
war = self.app.model.get_war_by_round(round_id)
|
||||||
workflow = RoundClosureWorkflow(self.app)
|
workflow = RoundClosureWorkflow(self.app)
|
||||||
try:
|
confirmed = False
|
||||||
workflow.start(war, camp, rnd)
|
stop = False
|
||||||
except DomainError as e:
|
while True:
|
||||||
QMessageBox.warning(
|
try:
|
||||||
self.app.view,
|
workflow.start(war, camp, rnd, confirmed)
|
||||||
"Closure forbidden",
|
break
|
||||||
str(e),
|
except RequiresConfirmation as e:
|
||||||
)
|
reply = QMessageBox.question(
|
||||||
|
self.app.view,
|
||||||
|
"Confirm closing",
|
||||||
|
str(e),
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
|
)
|
||||||
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
|
if e.action:
|
||||||
|
e.action()
|
||||||
|
confirmed = True
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
stop = True
|
||||||
|
break
|
||||||
|
except DomainError as e:
|
||||||
|
QMessageBox.warning(
|
||||||
|
self.app.view,
|
||||||
|
"Closure forbidden",
|
||||||
|
str(e),
|
||||||
|
)
|
||||||
|
stop = True
|
||||||
|
break
|
||||||
|
if stop:
|
||||||
return
|
return
|
||||||
self.app.is_dirty = True
|
self.app.is_dirty = True
|
||||||
self.app.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS)
|
self.app.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS)
|
||||||
|
|
@ -234,7 +256,8 @@ class RoundController:
|
||||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
)
|
)
|
||||||
if reply == QMessageBox.StandardButton.Yes:
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
e.action()
|
if e.action:
|
||||||
|
e.action()
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
self.app.is_dirty = True
|
self.app.is_dirty = True
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,8 @@ class WarController:
|
||||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
)
|
)
|
||||||
if reply == QMessageBox.StandardButton.Yes:
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
e.action()
|
if e.action:
|
||||||
|
e.action()
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
self.is_dirty = True
|
self.is_dirty = True
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,10 @@ class Workflow:
|
||||||
|
|
||||||
class RoundClosureWorkflow(Workflow):
|
class RoundClosureWorkflow(Workflow):
|
||||||
|
|
||||||
def start(self, war: War, campaign: Campaign, round: Round) -> None:
|
def start(
|
||||||
Closer.check_round_closable(round)
|
self, war: War, campaign: Campaign, round: Round, confirmed: bool = False
|
||||||
|
) -> None:
|
||||||
|
Closer.check_round_closable(round, confirmed)
|
||||||
ties = TieBreaker.find_battle_ties(war, round.id)
|
ties = TieBreaker.find_battle_ties(war, round.id)
|
||||||
while ties:
|
while ties:
|
||||||
for tie in ties:
|
for tie in ties:
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,12 @@ class Battle:
|
||||||
def is_finished(self) -> bool:
|
def is_finished(self) -> bool:
|
||||||
return self.winner_id is not None or self.is_draw()
|
return self.winner_id is not None or self.is_draw()
|
||||||
|
|
||||||
|
def has_player(self) -> bool:
|
||||||
|
return self.player_1_id is not None or self.player_2_id is not None
|
||||||
|
|
||||||
|
def is_complete(self) -> bool:
|
||||||
|
return self.player_1_id is not None and self.player_2_id is not None
|
||||||
|
|
||||||
def toDict(self) -> Dict[str, Any]:
|
def toDict(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"sector_id": self.sector_id,
|
"sector_id": self.sector_id,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from warchron.constants import ContextType
|
from warchron.constants import ContextType
|
||||||
from warchron.model.exception import ForbiddenOperation
|
from warchron.model.exception import ForbiddenOperation, RequiresConfirmation
|
||||||
from warchron.model.war_event import InfluenceGained
|
from warchron.model.war_event import InfluenceGained
|
||||||
from warchron.model.war import War
|
from warchron.model.war import War
|
||||||
from warchron.model.campaign import Campaign
|
from warchron.model.campaign import Campaign
|
||||||
|
|
@ -14,13 +14,19 @@ class Closer:
|
||||||
# Round methods
|
# Round methods
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_round_closable(round: Round) -> None:
|
def check_round_closable(round: Round, confirmed: bool) -> None:
|
||||||
if round.is_over:
|
if round.is_over:
|
||||||
raise ForbiddenOperation("Round already closed")
|
raise ForbiddenOperation("Round already closed")
|
||||||
if not round.all_battles_finished():
|
if not confirmed:
|
||||||
raise ForbiddenOperation(
|
if any(not bat.is_complete() for bat in round.battles.values()):
|
||||||
"All battles must be finished to close their round"
|
raise RequiresConfirmation(
|
||||||
)
|
"Battle(s) in this round miss player(s).\n"
|
||||||
|
"Do you want to continue?",
|
||||||
|
)
|
||||||
|
if not round.all_battles_finished():
|
||||||
|
raise ForbiddenOperation(
|
||||||
|
"All battles must be finished to close their round"
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def apply_battle_outcomes(war: War, campaign: Campaign, battle: Battle) -> None:
|
def apply_battle_outcomes(war: War, campaign: Campaign, battle: Battle) -> None:
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,6 @@ class DomainDecision(Exception):
|
||||||
|
|
||||||
|
|
||||||
class RequiresConfirmation(DomainDecision):
|
class RequiresConfirmation(DomainDecision):
|
||||||
def __init__(self, message: str, action: Callable[[], None]):
|
def __init__(self, message: str, action: Callable[[], None] | None = None):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
self.action = action
|
self.action = action
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,7 @@ class Pairing:
|
||||||
bat.set_winner(None)
|
bat.set_winner(None)
|
||||||
war.revert_choice_ties(round.id)
|
war.revert_choice_ties(round.id)
|
||||||
|
|
||||||
if any(
|
if any(bat.has_player() for bat in round.battles.values()):
|
||||||
bat.player_1_id is not None or bat.player_2_id is not None
|
|
||||||
for bat in round.battles.values()
|
|
||||||
):
|
|
||||||
raise RequiresConfirmation(
|
raise RequiresConfirmation(
|
||||||
"Battle(s) already have player(s) assigned for this round.\n"
|
"Battle(s) already have player(s) assigned for this round.\n"
|
||||||
"Battle players will be cleared.\n"
|
"Battle players will be cleared.\n"
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,6 @@ 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()
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,9 @@ class TieBreaker:
|
||||||
for battle in round.battles.values():
|
for battle in round.battles.values():
|
||||||
if campaign is None:
|
if campaign is None:
|
||||||
raise DomainError("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:
|
if not battle.is_complete():
|
||||||
raise DomainError("Missing player(s) in this battle context.")
|
continue
|
||||||
|
assert battle.player_1_id is not None and battle.player_2_id is not None
|
||||||
p1_id = campaign.campaign_to_war_part_id(battle.player_1_id)
|
p1_id = campaign.campaign_to_war_part_id(battle.player_1_id)
|
||||||
p2_id = campaign.campaign_to_war_part_id(battle.player_2_id)
|
p2_id = campaign.campaign_to_war_part_id(battle.player_2_id)
|
||||||
if not battle.is_draw():
|
if not battle.is_draw():
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue