Move calculation of aircraft amounts into game.

The planner needs to know how much space is still expected to be
available next turn.
This commit is contained in:
Dan Albert 2020-11-20 18:25:03 -08:00
parent f8b2dbe283
commit c4b8a41742
7 changed files with 44 additions and 48 deletions

View File

@ -352,11 +352,13 @@ class Event:
logging.info(info.text) logging.info(info.text)
class UnitsDeliveryEvent(Event): class UnitsDeliveryEvent(Event):
informational = True 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, super(UnitsDeliveryEvent, self).__init__(game=game,
location=to_cp.position, location=to_cp.position,
from_cp=from_cp, from_cp=from_cp,
@ -364,17 +366,16 @@ class UnitsDeliveryEvent(Event):
attacker_name=attacker_name, attacker_name=attacker_name,
defender_name=defender_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) 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(): for k, v in units.items():
self.units[k] = self.units.get(k, 0) + v self.units[k] = self.units.get(k, 0) + v
def skip(self): def skip(self) -> None:
for k, v in self.units.items(): 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) info = Information("Ally Reinforcement", str(k.id) + " x " + str(v) + " at " + self.to_cp.name, self.game.turn)
self.game.informations.append(info) self.game.informations.append(info)

View File

@ -316,7 +316,7 @@ class Game:
if i > 50 or budget_for_aircraft <= 0: if i > 50 or budget_for_aircraft <= 0:
break break
target_cp = random.choice(potential_cp_armor) target_cp = random.choice(potential_cp_armor)
if target_cp.base.total_planes >= MAX_AIRCRAFT: if target_cp.base.total_aircraft >= MAX_AIRCRAFT:
continue continue
unit = random.choice(potential_units) unit = random.choice(potential_units)
price = db.PRICES[unit] * 2 price = db.PRICES[unit] * 2

View File

@ -4,9 +4,8 @@ import math
import typing import typing
from typing import Dict, Type from typing import Dict, Type
from dcs.planes import PlaneType
from dcs.task import CAP, CAS, Embarking, PinpointStrike, Task 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 dcs.vehicles import AirDefence, Armor
from game import db from game import db
@ -21,20 +20,16 @@ BASE_MIN_STRENGTH = 0
class Base: 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): def __init__(self):
self.aircraft = {} self.aircraft: Dict[FlyingType, int] = {}
self.armor = {} self.armor: Dict[VehicleType, int] = {}
self.aa = {} self.aa: Dict[AirDefence, int] = {}
self.commision_points: Dict[Type, float] = {} self.commision_points: Dict[Type, float] = {}
self.strength = 1 self.strength = 1
@property @property
def total_planes(self) -> int: def total_aircraft(self) -> int:
return sum(self.aircraft.values()) return sum(self.aircraft.values())
@property @property
@ -83,7 +78,7 @@ class Base:
logging.info("{} for {} ({}): {}".format(self, for_type, count, result)) logging.info("{} for {} ({}): {}".format(self, for_type, count, result))
return 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) return self._find_best_unit(self.aircraft, for_type, count)
def _find_best_armor(self, for_type: Task, count: int) -> typing.Dict[Armor, int]: def _find_best_armor(self, for_type: Task, count: int) -> typing.Dict[Armor, int]:
@ -155,7 +150,7 @@ class Base:
if task: if task:
count = sum([v for k, v in self.aircraft.items() if db.unit_task(k) == task]) count = sum([v for k, v in self.aircraft.items() if db.unit_task(k) == task])
else: else:
count = self.total_planes count = self.total_aircraft
count = int(math.ceil(count * PLANES_SCRAMBLE_FACTOR * self.strength)) 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) 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. # previous logic removed because we always want the full air defense capabilities.
return self.total_aa 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)) return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP))
def scramble_last_defense(self): def scramble_last_defense(self):
# return as many CAP-capable aircraft as we can since this is the last defense of the base # 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) # (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)) 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)) return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP))
def assemble_attack(self) -> typing.Dict[Armor, int]: def assemble_attack(self) -> typing.Dict[Armor, int]:

View File

@ -376,6 +376,15 @@ class ControlPoint(MissionTarget):
return False return False
return True 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): class OffMapSpawn(ControlPoint):
def __init__(self, id: int, name: str, position: Point): def __init__(self, id: int, name: str, position: Point):

View File

@ -57,7 +57,7 @@ class QBaseMenu2(QDialog):
title = QLabel("<b>" + self.cp.name + "</b>") title = QLabel("<b>" + self.cp.name + "</b>")
title.setAlignment(Qt.AlignLeft | Qt.AlignTop) title.setAlignment(Qt.AlignLeft | Qt.AlignTop)
title.setProperty("style", "base-title") 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")) "Available" if self.cp.has_runway() else "Unavailable"))
self.topLayout.addWidget(title) self.topLayout.addWidget(title)
self.topLayout.addWidget(unitsPower) self.topLayout.addWidget(unitsPower)

View File

@ -126,13 +126,6 @@ class QRecruitBehaviour:
QRecruitBehaviour.BUDGET_FORMAT.format(self.budget)) QRecruitBehaviour.BUDGET_FORMAT.format(self.budget))
def buy(self, unit_type): 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] price = db.PRICES[unit_type]
if self.budget >= price: if self.budget >= price:
self.pending_deliveries.deliver({unit_type: 1}) self.pending_deliveries.deliver({unit_type: 1})
@ -158,19 +151,6 @@ class QRecruitBehaviour:
self._update_count_label(unit_type) self._update_count_label(unit_type)
self.update_available_budget() 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): def set_maximum_units(self, maximum_units):
""" """
Set the maximum number of units that can be bought Set the maximum number of units that can be bought

View File

@ -1,3 +1,4 @@
import logging
from typing import Optional, Set from typing import Optional, Set
from PySide2.QtCore import Qt from PySide2.QtCore import Qt
@ -37,7 +38,7 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
self.bought_amount_labels = {} self.bought_amount_labels = {}
self.existing_units_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() self.init_ui()
@ -80,8 +81,18 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
self.setLayout(main_layout) self.setLayout(main_layout)
def buy(self, unit_type): 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) 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): def sell(self, unit_type: UnitType):
# Don't need to remove aircraft from the inventory if we're canceling # 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) "assigned to a mission?", QMessageBox.Ok)
return return
super().sell(unit_type) 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): class QHangarStatus(QHBoxLayout):