From c4b8a4174215dd164d86cef02dee162c7e02ea9a Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 18:25:03 -0800 Subject: [PATCH] Move calculation of aircraft amounts into game. The planner needs to know how much space is still expected to be available next turn. --- game/event/event.py | 15 ++++++----- game/game.py | 2 +- game/theater/base.py | 27 ++++++++----------- game/theater/controlpoint.py | 9 +++++++ qt_ui/windows/basemenu/QBaseMenu2.py | 2 +- qt_ui/windows/basemenu/QRecruitBehaviour.py | 20 -------------- .../airfield/QAircraftRecruitmentMenu.py | 17 +++++++++--- 7 files changed, 44 insertions(+), 48 deletions(-) diff --git a/game/event/event.py b/game/event/event.py index ea5f3b80..c4506fab 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -352,11 +352,13 @@ class Event: logging.info(info.text) - class UnitsDeliveryEvent(Event): + informational = True - def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game): + def __init__(self, attacker_name: str, defender_name: str, + from_cp: ControlPoint, to_cp: ControlPoint, + game: Game) -> None: super(UnitsDeliveryEvent, self).__init__(game=game, location=to_cp.position, from_cp=from_cp, @@ -364,17 +366,16 @@ class UnitsDeliveryEvent(Event): attacker_name=attacker_name, defender_name=defender_name) - self.units: Dict[UnitType, int] = {} + self.units: Dict[Type[UnitType], int] = {} - def __str__(self): + def __str__(self) -> str: return "Pending delivery to {}".format(self.to_cp) - def deliver(self, units: Dict[UnitType, int]): + def deliver(self, units: Dict[Type[UnitType], int]) -> None: for k, v in units.items(): self.units[k] = self.units.get(k, 0) + v - def skip(self): - + def skip(self) -> None: for k, v in self.units.items(): info = Information("Ally Reinforcement", str(k.id) + " x " + str(v) + " at " + self.to_cp.name, self.game.turn) self.game.informations.append(info) diff --git a/game/game.py b/game/game.py index 3b29c46c..dd03be4f 100644 --- a/game/game.py +++ b/game/game.py @@ -316,7 +316,7 @@ class Game: if i > 50 or budget_for_aircraft <= 0: break target_cp = random.choice(potential_cp_armor) - if target_cp.base.total_planes >= MAX_AIRCRAFT: + if target_cp.base.total_aircraft >= MAX_AIRCRAFT: continue unit = random.choice(potential_units) price = db.PRICES[unit] * 2 diff --git a/game/theater/base.py b/game/theater/base.py index 47b3580e..40f604fc 100644 --- a/game/theater/base.py +++ b/game/theater/base.py @@ -4,9 +4,8 @@ import math import typing from typing import Dict, Type -from dcs.planes import PlaneType from dcs.task import CAP, CAS, Embarking, PinpointStrike, Task -from dcs.unittype import UnitType, VehicleType +from dcs.unittype import FlyingType, UnitType, VehicleType from dcs.vehicles import AirDefence, Armor from game import db @@ -21,20 +20,16 @@ BASE_MIN_STRENGTH = 0 class Base: - aircraft = {} # type: typing.Dict[PlaneType, int] - armor = {} # type: typing.Dict[VehicleType, int] - aa = {} # type: typing.Dict[AirDefence, int] - strength = 1 # type: float def __init__(self): - self.aircraft = {} - self.armor = {} - self.aa = {} + self.aircraft: Dict[FlyingType, int] = {} + self.armor: Dict[VehicleType, int] = {} + self.aa: Dict[AirDefence, int] = {} self.commision_points: Dict[Type, float] = {} self.strength = 1 @property - def total_planes(self) -> int: + def total_aircraft(self) -> int: return sum(self.aircraft.values()) @property @@ -83,7 +78,7 @@ class Base: logging.info("{} for {} ({}): {}".format(self, for_type, count, result)) return result - def _find_best_planes(self, for_type: Task, count: int) -> typing.Dict[PlaneType, int]: + def _find_best_planes(self, for_type: Task, count: int) -> typing.Dict[FlyingType, int]: return self._find_best_unit(self.aircraft, for_type, count) def _find_best_armor(self, for_type: Task, count: int) -> typing.Dict[Armor, int]: @@ -155,7 +150,7 @@ class Base: if task: count = sum([v for k, v in self.aircraft.items() if db.unit_task(k) == task]) else: - count = self.total_planes + count = self.total_aircraft count = int(math.ceil(count * PLANES_SCRAMBLE_FACTOR * self.strength)) return min(min(max(count, PLANES_SCRAMBLE_MIN_BASE), int(PLANES_SCRAMBLE_MAX_BASE * multiplier)), count) @@ -167,18 +162,18 @@ class Base: # previous logic removed because we always want the full air defense capabilities. return self.total_aa - def scramble_sweep(self, multiplier: float) -> typing.Dict[PlaneType, int]: + def scramble_sweep(self, multiplier: float) -> typing.Dict[FlyingType, int]: return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP)) def scramble_last_defense(self): # return as many CAP-capable aircraft as we can since this is the last defense of the base # (but not more than 20 - that's just nuts) - return self._find_best_planes(CAP, min(self.total_planes, 20)) + return self._find_best_planes(CAP, min(self.total_aircraft, 20)) - def scramble_cas(self, multiplier: float) -> typing.Dict[PlaneType, int]: + def scramble_cas(self, multiplier: float) -> typing.Dict[FlyingType, int]: return self._find_best_planes(CAS, self.scramble_count(multiplier, CAS)) - def scramble_interceptors(self, multiplier: float) -> typing.Dict[PlaneType, int]: + def scramble_interceptors(self, multiplier: float) -> typing.Dict[FlyingType, int]: return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP)) def assemble_attack(self) -> typing.Dict[Armor, int]: diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index ca21d463..87cf20ed 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -376,6 +376,15 @@ class ControlPoint(MissionTarget): return False return True + @property + def expected_aircraft_next_turn(self) -> int: + total = self.base.total_aircraft + assert self.pending_unit_deliveries + for unit_bought in self.pending_unit_deliveries.units: + if issubclass(unit_bought, FlyingType): + total += self.pending_unit_deliveries.units[unit_bought] + return total + class OffMapSpawn(ControlPoint): def __init__(self, id: int, name: str, position: Point): diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index f9f7c159..3740448a 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -57,7 +57,7 @@ class QBaseMenu2(QDialog): title = QLabel("" + self.cp.name + "") title.setAlignment(Qt.AlignLeft | Qt.AlignTop) title.setProperty("style", "base-title") - unitsPower = QLabel("{} / {} / Runway : {}".format(self.cp.base.total_planes, self.cp.base.total_armor, + unitsPower = QLabel("{} / {} / Runway : {}".format(self.cp.base.total_aircraft, self.cp.base.total_armor, "Available" if self.cp.has_runway() else "Unavailable")) self.topLayout.addWidget(title) self.topLayout.addWidget(unitsPower) diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/QRecruitBehaviour.py index 5cb26a81..8e0e77d3 100644 --- a/qt_ui/windows/basemenu/QRecruitBehaviour.py +++ b/qt_ui/windows/basemenu/QRecruitBehaviour.py @@ -126,13 +126,6 @@ class QRecruitBehaviour: QRecruitBehaviour.BUDGET_FORMAT.format(self.budget)) def buy(self, unit_type): - - if self.maximum_units > 0: - if self.total_units + 1 > self.maximum_units: - logging.info("Not enough space left !") - # TODO : display modal warning - return - price = db.PRICES[unit_type] if self.budget >= price: self.pending_deliveries.deliver({unit_type: 1}) @@ -158,19 +151,6 @@ class QRecruitBehaviour: self._update_count_label(unit_type) self.update_available_budget() - @property - def total_units(self): - total = 0 - for unit_type in self.recruitables_types: - total += self.cp.base.total_units(unit_type) - - if self.pending_deliveries: - for unit_bought in self.pending_deliveries.units: - if db.unit_task(unit_bought) in self.recruitables_types: - total += self.pending_deliveries.units[unit_bought] - - return total - def set_maximum_units(self, maximum_units): """ Set the maximum number of units that can be bought diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index 3e8fef9f..3ece7855 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -1,3 +1,4 @@ +import logging from typing import Optional, Set from PySide2.QtCore import Qt @@ -37,7 +38,7 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): self.bought_amount_labels = {} self.existing_units_labels = {} - self.hangar_status = QHangarStatus(self.total_units, self.cp.available_aircraft_slots) + self.hangar_status = QHangarStatus(self.total_aircraft, self.cp.available_aircraft_slots) self.init_ui() @@ -80,8 +81,18 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): self.setLayout(main_layout) def buy(self, unit_type): + if self.maximum_units > 0: + if self.total_aircraft + 1 > self.maximum_units: + logging.debug(f"No space for additional aircraft at {self.cp}.") + return + super().buy(unit_type) - self.hangar_status.update_label(self.total_units, self.cp.available_aircraft_slots) + self.hangar_status.update_label(self.total_aircraft, + self.cp.available_aircraft_slots) + + @property + def total_aircraft(self) -> int: + return self.cp.expected_aircraft_next_turn def sell(self, unit_type: UnitType): # Don't need to remove aircraft from the inventory if we're canceling @@ -99,7 +110,7 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): "assigned to a mission?", QMessageBox.Ok) return super().sell(unit_type) - self.hangar_status.update_label(self.total_units, self.cp.available_aircraft_slots) + self.hangar_status.update_label(self.total_aircraft, self.cp.available_aircraft_slots) class QHangarStatus(QHBoxLayout):