From 74142536e92b9a11d2fbfdab1dac4c931a849b06 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 14 Aug 2022 13:49:47 +0200 Subject: [PATCH] #978 --- game/game.py | 11 +++++----- qt_ui/widgets/QTopPanel.py | 28 ++++++++++++++++++------ qt_ui/widgets/simspeedcontrols.py | 5 +++++ qt_ui/windows/GameUpdateSignal.py | 7 ++++++ qt_ui/windows/QDebriefingWindow.py | 9 +++++++- qt_ui/windows/QLiberationWindow.py | 32 +++++++++++++++++++++++++++- qt_ui/windows/basemenu/QBaseMenu2.py | 3 +++ 7 files changed, 82 insertions(+), 13 deletions(-) diff --git a/game/game.py b/game/game.py index a56c7d21..43dc927b 100644 --- a/game/game.py +++ b/game/game.py @@ -387,16 +387,17 @@ class Game: for_red: True if opfor should be re-initialized. for_blue: True if the player coalition should be re-initialized. """ + # Check for win or loss condition FIRST! + turn_state = self.check_win_loss() + if turn_state in (TurnState.LOSS, TurnState.WIN): + return self.process_win_loss(turn_state) + + # Update bullseye positions for blue & red self.set_bullseye() # Update statistics self.game_stats.update(self) - # Check for win or loss condition - turn_state = self.check_win_loss() - if turn_state in (TurnState.LOSS, TurnState.WIN): - return self.process_win_loss(turn_state) - # Plan flights & combat for next turn with logged_duration("Threat zone computation"): self.compute_threat_zones(events) diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py index 28f7d1ea..f4099add 100644 --- a/qt_ui/widgets/QTopPanel.py +++ b/qt_ui/widgets/QTopPanel.py @@ -11,6 +11,7 @@ from PySide2.QtWidgets import ( import qt_ui.uiconstants as CONST from game import Game, persistency +from game.game import TurnState from game.ato.package import Package from game.profiling import logged_duration from game.utils import meters @@ -78,14 +79,24 @@ class QTopPanel(QFrame): self.buttonBoxLayout.addWidget(self.transfers) self.buttonBox.setLayout(self.buttonBoxLayout) + self.simSpeedControls = SimSpeedControls(sim_controller) + self.proceedBox = QGroupBox("Proceed") self.proceedBoxLayout = QHBoxLayout() - self.proceedBoxLayout.addLayout(SimSpeedControls(sim_controller)) + self.proceedBoxLayout.addLayout(self.simSpeedControls) self.proceedBoxLayout.addLayout(MaxPlayerCount(self.game_model.ato_model)) self.proceedBoxLayout.addWidget(self.passTurnButton) self.proceedBoxLayout.addWidget(self.proceedButton) self.proceedBox.setLayout(self.proceedBoxLayout) + self.controls = [ + self.air_wing, + self.transfers, + self.simSpeedControls, + self.passTurnButton, + self.proceedButton, + ] + self.layout = QHBoxLayout() self.layout.addWidget(self.factionsInfos) @@ -127,15 +138,16 @@ class QTopPanel(QFrame): self.budgetBox.setGame(game) self.factionsInfos.setGame(game) - self.passTurnButton.setEnabled(True) - if game and game.turn > 0: - self.passTurnButton.setText("Pass Turn") + self.setControls(True) - if game and game.turn == 0: + if game.turn > 0: + self.passTurnButton.setText("Pass Turn") + elif game.turn == 0: self.passTurnButton.setText("Begin Campaign") self.proceedButton.setEnabled(False) + self.simSpeedControls.setEnabled(False) else: - self.proceedButton.setEnabled(True) + raise RuntimeError(f"game.turn out of bounds!\n value = {game.turn}") def open_air_wing(self): self.dialog = AirWingDialog(self.game_model, self.window()) @@ -291,3 +303,7 @@ class QTopPanel(QFrame): def budget_update(self, game: Game): self.budgetBox.setGame(game) + + def setControls(self, enabled: bool): + for controller in self.controls: + controller.setEnabled(enabled) diff --git a/qt_ui/widgets/simspeedcontrols.py b/qt_ui/widgets/simspeedcontrols.py index 39cdb937..ccf4196f 100644 --- a/qt_ui/widgets/simspeedcontrols.py +++ b/qt_ui/widgets/simspeedcontrols.py @@ -33,3 +33,8 @@ class SimSpeedControls(QHBoxLayout): def on_sim_speed_reset(self, speed_setting: SimSpeedSetting) -> None: self.buttons[speed_setting].setChecked(True) + + def setEnabled(self, enabled: bool) -> None: + super().setEnabled(enabled) + for button in self.button_group.buttons(): + button.setEnabled(enabled) diff --git a/qt_ui/windows/GameUpdateSignal.py b/qt_ui/windows/GameUpdateSignal.py index bb3c8a71..00e96264 100644 --- a/qt_ui/windows/GameUpdateSignal.py +++ b/qt_ui/windows/GameUpdateSignal.py @@ -5,6 +5,7 @@ from typing import Optional from PySide2.QtCore import QObject, Signal from game import Game +from game.game import TurnState from game.debriefing import Debriefing @@ -13,6 +14,7 @@ class GameUpdateSignal(QObject): instance = None gameupdated = Signal(Game) budgetupdated = Signal(Game) + game_state_changed = Signal(TurnState) debriefingReceived = Signal(Debriefing) game_loaded = Signal(Game) @@ -35,6 +37,11 @@ class GameUpdateSignal(QObject): # noinspection PyUnresolvedReferences self.debriefingReceived.emit(debriefing) + def gameStateChanged(self, state: TurnState): + if state in (TurnState.WIN, TurnState.LOSS): + # noinspection PyUnresolvedReferences + self.game_state_changed.emit(state) + @staticmethod def get_instance() -> GameUpdateSignal: return GameUpdateSignal.instance diff --git a/qt_ui/windows/QDebriefingWindow.py b/qt_ui/windows/QDebriefingWindow.py index f658a162..ab0e67f0 100644 --- a/qt_ui/windows/QDebriefingWindow.py +++ b/qt_ui/windows/QDebriefingWindow.py @@ -1,7 +1,7 @@ import logging from typing import Callable, Dict, TypeVar -from PySide2.QtGui import QIcon, QPixmap +from PySide2.QtGui import QIcon, QPixmap, QCloseEvent from PySide2.QtWidgets import ( QDialog, QGridLayout, @@ -12,6 +12,8 @@ from PySide2.QtWidgets import ( ) from game.debriefing import Debriefing +from game.game import TurnState +from qt_ui.windows.GameUpdateSignal import GameUpdateSignal T = TypeVar("T") @@ -86,3 +88,8 @@ class QDebriefingWindow(QDialog): okay = QPushButton("Okay") okay.clicked.connect(self.close) layout.addWidget(okay) + + def closeEvent(self, event: QCloseEvent) -> None: + super().closeEvent(event) + state = self.debriefing.game.check_win_loss() + GameUpdateSignal.get_instance().gameStateChanged(state) diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index 6597a41e..0dbfd882 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -21,6 +21,7 @@ from PySide2.QtWidgets import ( import qt_ui.uiconstants as CONST from game import Game, VERSION, persistency from game.debriefing import Debriefing +from game.game import TurnState from game.layout import LAYOUTS from game.server import EventStream, GameContext from game.server.dependencies import QtCallbacks, QtContext @@ -129,9 +130,10 @@ class QLiberationWindow(QMainWindow): hbox.setSizes([1, 10000000]) vbox.setSizes([600, 100]) + self.top_panel = QTopPanel(self.game_model, self.sim_controller) vbox = QVBoxLayout() vbox.setMargin(0) - vbox.addWidget(QTopPanel(self.game_model, self.sim_controller)) + vbox.addWidget(self.top_panel) vbox.addWidget(hbox) central_widget = QWidget() @@ -141,6 +143,7 @@ class QLiberationWindow(QMainWindow): def connectSignals(self): GameUpdateSignal.get_instance().gameupdated.connect(self.setGame) GameUpdateSignal.get_instance().debriefingReceived.connect(self.onDebriefing) + GameUpdateSignal.get_instance().game_state_changed.connect(self.onEndGame) def initActions(self): self.newGameAction = QAction("&New Game", self) @@ -368,6 +371,32 @@ class QLiberationWindow(QMainWindow): self.game = game GameUpdateSignal.get_instance().game_loaded.emit(self.game) + def onEndGame(self, state: TurnState): + if state == TurnState.CONTINUE: + return + + for window in QApplication.topLevelWidgets(): + if window is not self: + window.close() + + GameUpdateSignal.get_instance().updateGame(None) + + self.top_panel.setControls(False) + + title = "Victory!" if TurnState.WIN else "Defeat!" + msgvar = "won" if TurnState.WIN else "lost" + msg = f"You have {msgvar} the campaign, do you wish to start a new one?" + result = QMessageBox.information( + QApplication.focusWidget(), + title, + msg, + QMessageBox.Yes, + QMessageBox.No, + ) + + if result is not None and result == QMessageBox.Yes: + self.newGame() + def setGame(self, game: Optional[Game]): try: self.game = game @@ -522,6 +551,7 @@ class QLiberationWindow(QMainWindow): self._save_window_geometry() super().closeEvent(event) self.dialog = None + self.debriefing = None for window in QApplication.topLevelWidgets(): window.close() else: diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index 7eb1f5ae..5f56f3be 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -14,6 +14,7 @@ from dcs.ships import Stennis, KUZNECOW from game import Game from game.ato.flighttype import FlightType from game.config import RUNWAY_REPAIR_COST +from game.game import TurnState from game.server import EventStream from game.sim import GameUpdateEvents from game.theater import ( @@ -126,6 +127,8 @@ class QBaseMenu2(QDialog): self.game_model.game.initialize_turn(events) EventStream.put_nowait(events) GameUpdateSignal.get_instance().updateGame(self.game_model.game) + state = self.game_model.game.check_win_loss() + GameUpdateSignal.get_instance().gameStateChanged(state) @property def has_transfer_destinations(self) -> bool: