diff --git a/__init__.py b/__init__.py index ae9ff0be..1cf12453 100755 --- a/__init__.py +++ b/__init__.py @@ -1,90 +1,24 @@ #!/usr/bin/env python3 +import pickle -import dcs -import os - -import gen import theater.caucasus -import game.operation import ui.window import ui.mainmenu from game.game import Game from theater import start_generator -from theater.controlpoint import * +from userdata import persistency -from dcs.planes import * -from dcs.vehicles import * +game = persistency.restore_game() +if not game: + theater = theater.caucasus.CaucasusTheater() + start_generator.generate_initial(theater, "Russia") -m = dcs.Mission() -theater = theater.caucasus.CaucasusTheater() - -start_generator.generate_initial(theater, "Russia") -g = Game(theater=theater) + game = Game(theater=theater) w = ui.window.Window() -m = ui.mainmenu.MainMenu(w, None, g) +m = ui.mainmenu.MainMenu(w, None, game) m.display() w.run() - -""" -selected_cp = None # type: ControlPoint -while True: - ptr = 0 - - print("Budget: {}m".format(g.budget)) - - if selected_cp is None: - print("Events:") - for event in g.events: - ptr += 1 - print("{}. {} {}".format(ptr, event.attacker != g.side and "!" or " ", event)) - - print("Control Points:") - controlpoints = g.theater.controlpoints - controlpoints.sort(key=lambda x: x.captured) - for cp in g.theater.controlpoints: - ptr += 1 - print("{}. [{}{}] {}{}{}{}".format( - ptr, - cp.captured and "x" or " ", - int(cp.base.readiness * 10), - cp.name, - "^" * cp.base.total_planes, - "." * cp.base.total_armor, - "*" * cp.base.total_aa)) - - events_boundary = len(g.events) - try: - selected_idx = int(input(">").strip()) - 1 - except: - continue - - if selected_idx == -1: - g.pass_turn() - continue - if selected_idx < events_boundary: - event = g.events[selected_idx] - else: - selected_cp = controlpoints[selected_idx - events_boundary] - else: - print("Units on the base: ") - for unit, count in selected_cp.base.all_units: - print("{} ({}) ".format(unit.name and unit.name or unit.id, count), end="") - print("") - - try: - selected_idx = int(input(">").strip()) - 1 - except: - continue - if selected_idx == -1: - selected_cp = None - -if not os.path.exists("./build"): - os.mkdir("./build") - -m.save("build/output.miz") -""" - diff --git a/shop/db.py b/game/db.py similarity index 98% rename from shop/db.py rename to game/db.py index a4f126d0..3c6d2ea2 100644 --- a/shop/db.py +++ b/game/db.py @@ -1,7 +1,4 @@ import typing -import dcs - -import globals from dcs.vehicles import * from dcs.unitgroup import * diff --git a/game/event.py b/game/event.py index 4a73b060..33785eb2 100644 --- a/game/event.py +++ b/game/event.py @@ -1,11 +1,3 @@ -import typing -import random -import math - -import dcs - -from theater.controlpoint import * -from userdata.debriefing_parser import * from game.operation import * DIFFICULTY_LOG_BASE = 1.5 @@ -18,12 +10,12 @@ class Event: difficulty = 1 # type: int BONUS_BASE = 0 - def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint): - self.mission = dcs.mission.Mission() - self.attacker = self.mission.country(attacker_name) - self.defender = self.mission.country(defender_name) + def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): + self.attacker_name = attacker_name + self.defender_name = defender_name self.to_cp = to_cp self.from_cp = from_cp + self.theater = theater def bonus(self) -> int: return math.ceil(math.log(self.difficulty, DIFFICULTY_LOG_BASE) * self.BONUS_BASE) @@ -31,10 +23,19 @@ class Event: def is_successfull(self, debriefing: Debriefing) -> bool: return self.operation.is_successfull(debriefing) + def generate(self): + self.operation.prepare(is_quick=False) + self.operation.generate() + self.operation.mission.save("build/nextturn.miz") + + self.operation.prepare(is_quick=True) + self.operation.generate() + self.operation.mission.save('build/nextturn_quick.miz') + def commit(self, debriefing: Debriefing): for country, losses in debriefing.destroyed_units.items(): cp = None # type: ControlPoint - if country == self.attacker.name: + if country == self.attacker_name: cp = self.from_cp else: cp = self.to_cp @@ -84,27 +85,29 @@ class GroundInterceptEvent(Event): pass def player_attacking(self, position: Point, strikegroup: db.PlaneDict, clients: db.PlaneDict): - suitable_unittypes = db.find_unittype(CAP, self.defender.name) + suitable_unittypes = db.find_unittype(CAP, self.defender_name) random.shuffle(suitable_unittypes) unittypes = suitable_unittypes[:self.TARGET_VARIETY] typecount = max(math.floor(self.difficulty * self.TARGET_AMOUNT_FACTOR), 1) - self.targets = {unittype: typecount for unittype in unittypes} - self.operation = GroundInterceptOperation(mission=self.mission, - attacker=self.attacker, - defender=self.defender, - attacker_clients=clients, - defender_clients={}, - from_cp=self.from_cp, - position=position, - target=self.targets, - strikegroup=strikegroup) + + op = GroundInterceptOperation(attacker_name=self.attacker_name, + defender_name=self.defender_name, + attacker_clients=clients, + defender_clients={}, + from_cp=self.from_cp) + op.setup(position=position, + target=self.targets, + strikegroup=strikegroup) + + self.operation = op class InterceptEvent(Event): ESCORT_AMOUNT_FACTOR = 2 BONUS_BASE = 5 STRENGTH_INFLUENCE = 0.25 + GLOBAL_STRENGTH_INFLUENCE = 0.05 AIRDEFENSE_COUNT = 3 transport_unit = None # type: FlyingType @@ -123,7 +126,11 @@ class InterceptEvent(Event): super(InterceptEvent, self).commit(debriefing) if self.is_successfull(debriefing): - self.to_cp.base.affect_strength(self.STRENGTH_INFLUENCE * float(self.from_cp.captured and -1 or 1)) + if self.from_cp.is_global: + for cp in self.theater.enemy_points(): + cp.base.affect_strength(-self.GLOBAL_STRENGTH_INFLUENCE) + else: + self.to_cp.base.affect_strength(self.STRENGTH_INFLUENCE * float(self.from_cp.captured and -1 or 1)) else: self.to_cp.base.affect_strength(self.STRENGTH_INFLUENCE * float(self.from_cp.captured and 1 or -1)) @@ -133,39 +140,43 @@ class InterceptEvent(Event): def player_attacking(self, interceptors: db.PlaneDict, clients: db.PlaneDict): escort = self.to_cp.base.scramble_sweep(self.to_cp) - self.transport_unit = random.choice(db.find_unittype(Transport, self.defender.name)) + self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name)) assert self.transport_unit is not None - airdefense_unit = db.find_unittype(AirDefence, self.defender.name)[0] + airdefense_unit = db.find_unittype(AirDefence, self.defender_name)[0] - self.operation = InterceptOperation(mission=self.mission, - attacker=self.attacker, - defender=self.defender, - attacker_clients=clients, - defender_clients={}, - from_cp=self.from_cp, - to_cp=self.to_cp, - escort=escort, - transport={self.transport_unit: 1}, - airdefense={airdefense_unit: self.AIRDEFENSE_COUNT}, - interceptors=interceptors) + op = InterceptOperation(attacker_name=self.attacker_name, + defender_name=self.defender_name, + attacker_clients=clients, + defender_clients={}, + from_cp=self.from_cp, + to_cp=self.to_cp) + + op.setup(escort=escort, + transport={self.transport_unit: 1}, + airdefense={airdefense_unit: self.AIRDEFENSE_COUNT}, + interceptors=interceptors) + + self.operation = op def player_defending(self, escort: db.PlaneDict, clients: db.PlaneDict): interceptors = self.from_cp.base.scramble_interceptors_count(self.difficulty * self.ESCORT_AMOUNT_FACTOR) - self.transport_unit = random.choice(db.find_unittype(Transport, self.defender.name)) + self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name)) assert self.transport_unit is not None - self.operation = InterceptOperation(mission=self.mission, - attacker=self.attacker, - defender=self.defender, - attacker_clients={}, - defender_clients=clients, - from_cp=self.from_cp, - to_cp=self.to_cp, - escort=escort, - transport={self.transport_unit: 1}, - interceptors=interceptors, - airdefense={}) + op = InterceptOperation(attacker_name=self.attacker_name, + defender_name=self.defender_name, + attacker_clients={}, + defender_clients=clients, + from_cp=self.from_cp, + to_cp=self.to_cp) + + op.setup(escort=escort, + transport={self.transport_unit: 1}, + interceptors=interceptors, + airdefense={}) + + self.operation = op class CaptureEvent(Event): @@ -188,6 +199,9 @@ class CaptureEvent(Event): if self.is_successfull(debriefing): if self.from_cp.captured: self.to_cp.captured = True + self.to_cp.base.filter_units(db.UNIT_BY_COUNTRY[self.attacker_name]) + + self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY) else: if not self.from_cp.captured: self.to_cp.captured = False @@ -202,47 +216,52 @@ class CaptureEvent(Event): escort = self.from_cp.base.scramble_sweep(self.to_cp) attackers = self.from_cp.base.assemble_cap(self.to_cp) - self.operation = CaptureOperation(mission=self.mission, - attacker=self.attacker, - defender=self.defender, - attacker_clients={}, - defender_clients=clients, - from_cp=self.from_cp, - to_cp=self.to_cp, - cas=cas, - escort=escort, - attack=attackers, - intercept=interceptors, - defense=self.to_cp.base.armor, - aa=self.to_cp.base.aa) + op = CaptureOperation(attacker_name=self.attacker_name, + defender_name=self.defender_name, + attacker_clients={}, + defender_clients=clients, + from_cp=self.from_cp, + to_cp=self.to_cp) + + op.setup(cas=cas, + escort=escort, + attack=attackers, + intercept=interceptors, + defense=self.to_cp.base.armor, + aa=self.to_cp.base.aa) + + self.operation = op def player_attacking(self, cas: db.PlaneDict, escort: db.PlaneDict, armor: db.ArmorDict, clients: db.PlaneDict): interceptors = self.to_cp.base.scramble_sweep(for_target=self.to_cp) - self.operation = CaptureOperation(mission=self.mission, - attacker=self.attacker, - defender=self.defender, - attacker_clients=clients, - defender_clients={}, - from_cp=self.from_cp, - to_cp=self.to_cp, - cas=cas, - escort=escort, - attack=armor, - intercept=interceptors, - defense=self.to_cp.base.armor, - aa=self.to_cp.base.aa) + op = CaptureOperation(attacker_name=self.attacker_name, + defender_name=self.defender_name, + attacker_clients=clients, + defender_clients={}, + from_cp=self.from_cp, + to_cp=self.to_cp) + + op.setup(cas=cas, + escort=escort, + attack=armor, + intercept=interceptors, + defense=self.to_cp.base.armor, + aa=self.to_cp.base.aa) + + self.operation = op class UnitsDeliveryEvent(Event): informational = True units = None # type: typing.Dict[UnitType, int] - def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint): + def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): super(UnitsDeliveryEvent, self).__init__(attacker_name=attacker_name, defender_name=defender_name, from_cp=from_cp, - to_cp=to_cp) + to_cp=to_cp, + theater=theater) self.units = {} diff --git a/game/game.py b/game/game.py index 417a201e..0811ff80 100644 --- a/game/game.py +++ b/game/game.py @@ -1,9 +1,3 @@ -import typing -import random - -from theater.conflicttheater import * -from theater.controlpoint import * -from userdata.debriefing_parser import * from game.event import * COMMISION_LIMITS_SCALE = 2 @@ -24,12 +18,16 @@ COMMISION_AMOUNTS_FACTORS = { ENEMY_INTERCEPT_PROBABILITY_BASE = 10 +ENEMY_INTERCEPT_GLOBAL_PROBABILITY_BASE = 1 ENEMY_CAPTURE_PROBABILITY_BASE = 3 PLAYER_INTERCEPT_PROBABILITY_BASE = 30 PLAYER_GROUNDINTERCEPT_PROBABILITY_BASE = 30 PLAYER_GLOBALINTERCEPT_PROBABILITY_BASE = 100 +PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE = 50 +PLAYER_INTERCEPT_GLOBAL_PROBABILITY_LOG = 2 + PLAYER_BUDGET_BASE = 25 PLAYER_BUDGET_IMPORTANCE_LOG = 2 @@ -54,7 +52,8 @@ class Game: self.events.append(CaptureEvent(attacker_name=self.player, defender_name=self.enemy, from_cp=from_cp, - to_cp=to_cp)) + to_cp=to_cp, + theater=self.theater)) def _generate_enemy_caps(self): for from_cp, to_cp in self.theater.conflicts(False): @@ -65,10 +64,12 @@ class Game: self.events.append(CaptureEvent(attacker_name=self.enemy, defender_name=self.player, from_cp=from_cp, - to_cp=to_cp)) + to_cp=to_cp, + theater=self.theater)) break def _generate_interceptions(self): + enemy_interception = False for from_cp, to_cp in self.theater.conflicts(False): if from_cp.base.total_units(FighterSweep) == 0: continue @@ -77,15 +78,36 @@ class Game: self.events.append(InterceptEvent(attacker_name=self.enemy, defender_name=self.player, from_cp=from_cp, - to_cp=to_cp)) + to_cp=to_cp, + theater=self.theater)) + enemy_interception = True break + for to_cp in self.theater.player_points(): + if enemy_interception: + break + + if to_cp in self.theater.conflicts(False): + continue + + if self._roll(ENEMY_INTERCEPT_GLOBAL_PROBABILITY_BASE, 1): + for from_cp, _ in self.theater.conflicts(False): + if from_cp.base.total_units(FighterSweep) > 0: + self.events.append(InterceptEvent(attacker_name=self.enemy, + defender_name=self.player, + from_cp=from_cp, + to_cp=to_cp, + theater=self.theater)) + enemy_interception = True + break + for from_cp, to_cp in self.theater.conflicts(True): if self._roll(PLAYER_INTERCEPT_PROBABILITY_BASE, from_cp.base.strength): self.events.append(InterceptEvent(attacker_name=self.player, defender_name=self.enemy, from_cp=from_cp, - to_cp=to_cp)) + to_cp=to_cp, + theater=self.theater)) break def _generate_groundinterceptions(self): @@ -94,7 +116,20 @@ class Game: self.events.append(GroundInterceptEvent(attacker_name=self.player, defender_name=self.enemy, from_cp=from_cp, - to_cp=to_cp)) + to_cp=to_cp, + theater=self.theater)) + break + + def _generate_globalinterceptions(self): + for from_cp in [x for x in self.theater.player_points() if x.is_global]: + probability = PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE * math.log(len(self.theater.player_points()) + 1, PLAYER_INTERCEPT_GLOBAL_PROBABILITY_LOG) + if self._roll(probability, from_cp.base.strength): + to_cp = random.choice([x for x in self.theater.enemy_points() if x not in self.theater.conflicts()]) + self.events.append(InterceptEvent(attacker_name=self.player, + defender_name=self.enemy, + from_cp=from_cp, + to_cp=to_cp, + theater=self.theater)) break def _generate_global(self): @@ -134,7 +169,8 @@ class Game: event = UnitsDeliveryEvent(attacker_name=self.player, defender_name=self.player, from_cp=to_cp, - to_cp=to_cp) + to_cp=to_cp, + theater=self.theater) self.events.append(event) return event @@ -143,8 +179,8 @@ class Game: self.events.remove(event) def initiate_event(self, event: Event): - event.operation.generate() - event.mission.save("build/next_mission.miz") + assert event in self.events + event.generate() def finish_event(self, event: Event, debriefing: Debriefing): event.commit(debriefing) @@ -154,7 +190,7 @@ class Game: self.events.remove(event) def is_player_attack(self, event: Event): - return event.attacker.name == self.player + return event.attacker_name == self.player def pass_turn(self, no_action=False): for event in self.events: @@ -162,13 +198,14 @@ class Game: if not no_action: self._budget_player() - for cp in self.theater.enemy_bases(): + for cp in self.theater.enemy_points(): self._commision_units(cp) self.events = [] # type: typing.List[Event] self._fill_cap_events() self._generate_enemy_caps() self._generate_interceptions() + self._generate_globalinterceptions() self._generate_groundinterceptions() self._generate_global() diff --git a/game/operation.py b/game/operation.py index 83b1dfa4..354b0c06 100644 --- a/game/operation.py +++ b/game/operation.py @@ -1,14 +1,6 @@ -import typing - -from globals import * -from userdata.debriefing_parser import * -from dcs.mission import * -from dcs.unitgroup import * -from dcs.vehicles import * -from theater.controlpoint import * +from userdata.debriefing import * from theater.conflicttheater import * from theater.base import * -from shop import * from gen.armor import * from gen.aircraft import * @@ -18,13 +10,42 @@ from gen.conflictgen import * class Operation: - def __init__(self, mission: Mission, conflict: Conflict): + starting_position = None # type: db.StartingPosition + mission = None # type: dcs.Mission + conflict = None # type: Conflict + armorgen = None # type: ArmorConflictGenerator + airgen = None # type: AircraftConflictGenerator + aagen = None # type: AAConflictGenerator + shipgen = None # type: ShipGenerator + + def __init__(self, + attacker_name: str, + defender_name: str, + attacker_clients: db.PlaneDict, + defender_clients: db.PlaneDict, + from_cp: ControlPoint, + to_cp: ControlPoint = None): + self.attacker_name = attacker_name + self.defender_name = defender_name + self.attacker_clients = attacker_clients + self.defender_clients = defender_clients + self.from_cp = from_cp + self.to_cp = to_cp + + def initialize(self, mission: Mission, conflict: Conflict): self.mission = mission self.conflict = conflict - self.armorgen = ArmorConflictGenerator(self.mission, self.conflict) - self.airgen = AircraftConflictGenerator(self.mission, self.conflict) - self.aagen = AAConflictGenerator(self.mission, self.conflict) - self.shipgen = ShipGenerator(self.mission, self.conflict) + + self.armorgen = ArmorConflictGenerator(mission, conflict) + self.airgen = AircraftConflictGenerator(mission, conflict) + self.aagen = AAConflictGenerator(mission, conflict) + self.shipgen = ShipGenerator(mission, conflict) + + def prepare(self, is_quick: bool): + self.starting_position = is_quick and self.from_cp.at or None + + def generate(self): + pass def units_of(self, country_name: str) -> typing.Collection[UnitType]: return [] @@ -32,85 +53,81 @@ class Operation: def is_successfull(self, debriefing: Debriefing) -> bool: return True - def generate(self): - pass - class CaptureOperation(Operation): - def __init__(self, - mission: Mission, - attacker: Country, - defender: Country, - attacker_clients: db.PlaneDict, - defender_clients: db.PlaneDict, - from_cp: ControlPoint, - to_cp: ControlPoint, - cas: db.PlaneDict, - escort: db.PlaneDict, - attack: db.ArmorDict, - intercept: db.PlaneDict, - defense: db.ArmorDict, - aa: db.AirDefenseDict): - conflict = to_cp.conflict_attack(from_cp, attacker, defender) + cas = None # type: db.PlaneDict + escort = None # type: db.PlaneDict + intercept = None # type: db.PlaneDict + attack = None # type: db.ArmorDict + defense = None # type: db.ArmorDict + aa = None # type: db.AirDefenseDict - super(CaptureOperation, self).__init__(mission, conflict) - self.from_cp = from_cp - self.to_cp = to_cp - self.attacker_clients = attacker_clients - self.defender_clients = defender_clients - self.cas = cas - self.escort = escort - self.intercept = intercept + def setup(self, + cas: db.PlaneDict, + escort: db.PlaneDict, + attack: db.ArmorDict, + intercept: db.PlaneDict, + defense: db.ArmorDict, + aa: db.AirDefenseDict): + self.cas = cas + self.escort = escort + self.intercept = intercept + self.attack = attack + self.defense = defense + self.aa = aa - self.attack = attack - self.defense = defense + def prepare(self, is_quick: bool): + super(CaptureOperation, self).prepare(is_quick) + mission = dcs.Mission() - self.aa = aa + self.initialize(mission=mission, + conflict=self.to_cp.conflict_attack(self.from_cp, + mission.country(self.attacker_name), + mission.country(self.defender_name))) def generate(self): self.armorgen.generate(self.attack, self.defense) self.aagen.generate(self.aa) self.airgen.generate_defense(self.intercept, clients=self.defender_clients) - self.airgen.generate_cas(self.cas, clients=self.attacker_clients, at=self.from_cp.at) - self.airgen.generate_cas_escort(self.escort, clients=self.attacker_clients, at=self.from_cp.at) + self.airgen.generate_cas(self.cas, clients=self.attacker_clients, at=self.starting_position) + self.airgen.generate_cas_escort(self.escort, clients=self.attacker_clients, at=self.starting_position) class InterceptOperation(Operation): - def __init__(self, - mission: Mission, - attacker: Country, - defender: Country, - attacker_clients: db.PlaneDict, - defender_clients: db.PlaneDict, - from_cp: ControlPoint, - to_cp: ControlPoint, - escort: db.PlaneDict, - transport: db.PlaneDict, - airdefense: db.AirDefenseDict, - interceptors: db.PlaneDict): - heading = from_cp.position.heading_between_point(to_cp.position) - distance = from_cp.position.distance_to_point(to_cp.position) - position = from_cp.position.point_from_heading(heading, distance/2) - - conflict = Conflict.intercept_conflict( - attacker=attacker, - defender=defender, - position=position, - heading=heading, - radials=ALL_RADIALS - ) + escort = None # type: db.PlaneDict + transport = None # type: db.PlaneDict + interceptors = None # type: db.PlaneDict + airdefense = None # type: db.AirDefenseDict - super(InterceptOperation, self).__init__(mission, conflict) - self.to_cp = to_cp - self.from_cp = from_cp - self.attacker_clients = attacker_clients - self.defender_clients = defender_clients + def setup(self, + escort: db.PlaneDict, + transport: db.PlaneDict, + airdefense: db.AirDefenseDict, + interceptors: db.PlaneDict): self.escort = escort self.transport = transport self.airdefense = airdefense self.interceptors = interceptors + def prepare(self, is_quick: bool): + super(InterceptOperation, self).prepare(is_quick) + mission = dcs.Mission() + + heading = self.from_cp.position.heading_between_point(self.to_cp.position) + distance = self.from_cp.position.distance_to_point(self.to_cp.position) + position = self.from_cp.position.point_from_heading(heading, distance/2) + conflict = Conflict.intercept_conflict( + attacker=mission.country(self.attacker_name), + defender=mission.country(self.defender_name), + position=position, + heading=randint(0, 360), + radials=ALL_RADIALS + ) + + self.initialize(mission=mission, + conflict=conflict) + def generate(self): self.airgen.generate_transport(self.transport, self.to_cp.at) self.airgen.generate_transport_escort(self.escort, clients=self.defender_clients) @@ -119,35 +136,31 @@ class InterceptOperation(Operation): if self.from_cp.is_global: self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.shipgen.generate(self.from_cp.at)) else: - self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.from_cp.at) + self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.starting_position) class GroundInterceptOperation(Operation): - def __init__(self, - mission: Mission, - attacker: Country, - defender: Country, - from_cp: ControlPoint, - attacker_clients: db.PlaneDict, - defender_clients: db.PlaneDict, - position: Point, - target: db.ArmorDict, - strikegroup: db.PlaneDict): + def setup(self, + position: Point, + target: db.ArmorDict, + strikegroup: db.PlaneDict): + self.position = position + self.strikegroup = strikegroup + self.target = target + + def prepare(self, is_quick: bool): + super(GroundInterceptOperation, self).prepare(is_quick) + mission = dcs.Mission() conflict = Conflict.ground_intercept_conflict( - attacker=attacker, - defender=defender, - position=position, + attacker=mission.country(self.defender_name), + defender=mission.country(self.defender_name), + position=self.position, heading=randint(0, 360), radials=ALL_RADIALS ) super(GroundInterceptOperation, self).__init__(mission, conflict) - self.attacker_clients = attacker_clients - self.defender_clients = defender_clients - self.from_cp = from_cp - self.strikegroup = strikegroup - self.target = target def generate(self): - self.airgen.generate_cas(self.strikegroup, clients=self.attacker_clients, at=self.from_cp.at) + self.airgen.generate_cas(self.strikegroup, clients=self.attacker_clients, at=self.starting_position) self.armorgen.generate({}, self.target) diff --git a/gen/aaa.py b/gen/aaa.py index cfba29ef..596a558c 100644 --- a/gen/aaa.py +++ b/gen/aaa.py @@ -1,21 +1,9 @@ -import typing -import pdb -import dcs - -from random import randint - -import globals +from game import db from .conflictgen import * from .naming import * from dcs.mission import * -from dcs.vehicles import * -from dcs.unitgroup import * -from dcs.unittype import * -from dcs.mapping import * -from dcs.point import * -from dcs.task import * DISTANCE_FACTOR = 4, 5 @@ -24,7 +12,7 @@ class AAConflictGenerator: self.m = mission self.conflict = conflict - def generate(self, units: typing.Dict[UnitType, int]): + def generate(self, units: db.AirDefenseDict): for type, count in units.items(): for _, radial in zip(range(count), self.conflict.radials): distance = randint(self.conflict.size * DISTANCE_FACTOR[0], self.conflict.size * DISTANCE_FACTOR[1]) diff --git a/gen/aircraft.py b/gen/aircraft.py index 24da6603..c72d91a9 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -1,21 +1,10 @@ -import typing -import pdb -import dcs - -from random import randint - -import globals - -from shop import db +from game import db from .conflictgen import * from .naming import * from dcs.mission import * -from dcs.vehicles import * from dcs.unitgroup import * from dcs.unittype import * -from dcs.mapping import * -from dcs.point import * from dcs.task import * SPREAD_DISTANCE_FACTOR = 1, 2 diff --git a/gen/armor.py b/gen/armor.py index 61319f9b..c97f5fc0 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -1,20 +1,9 @@ -import typing -import pdb -import dcs - -from random import randint - -import globals - -from shop import db +from game import db from .conflictgen import * from .naming import * from dcs.mission import * -from dcs.vehicles import * -from dcs.unitgroup import * from dcs.unittype import * -from dcs.mapping import * from dcs.point import * from dcs.task import * from dcs.country import * diff --git a/gen/shipgen.py b/gen/shipgen.py index 7c04cd7f..eb62ab7d 100644 --- a/gen/shipgen.py +++ b/gen/shipgen.py @@ -1,20 +1,8 @@ -import typing -import pdb -import dcs - -from random import randint - -import globals - from .conflictgen import * from .naming import * from dcs.mission import * -from dcs.vehicles import * from dcs.unitgroup import * -from dcs.unittype import * -from dcs.mapping import * -from dcs.point import * from dcs.task import * diff --git a/globals.py b/globals.py deleted file mode 100644 index 540a4a5a..00000000 --- a/globals.py +++ /dev/null @@ -1,5 +0,0 @@ -import dcs - -MISSION = dcs.mission.Mission() -US = MISSION.country("USA") -THEM = MISSION.country("Russia") diff --git a/shop/__init__.py b/shop/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/theater/base.py b/theater/base.py index c727b383..1349cb5c 100644 --- a/theater/base.py +++ b/theater/base.py @@ -1,11 +1,8 @@ import typing import math -import random import itertools -import dcs - -from shop import db +from game import db from theater.controlpoint import ControlPoint from dcs.planes import * @@ -105,6 +102,10 @@ class Base: return 0 + def filter_units(self, applicable_units: typing.Collection): + self.aircraft = {k: v for k, v in self.aircraft.items() if k in applicable_units} + self.armor = {k: v for k, v in self.aircraft.items() if k in applicable_units} + def commision_units(self, units: typing.Dict[typing.Any, int]): for value in units.values(): assert value > 0 diff --git a/theater/conflicttheater.py b/theater/conflicttheater.py index 9aba464d..43b1fd61 100644 --- a/theater/conflicttheater.py +++ b/theater/conflicttheater.py @@ -40,5 +40,5 @@ class ConflictTheater: for connected_point in [x for x in cp.connected_points if x.captured != from_player]: yield (cp, connected_point) - def enemy_bases(self) -> typing.Collection[ControlPoint]: + def enemy_points(self) -> typing.Collection[ControlPoint]: return [point for point in self.controlpoints if not point.captured] diff --git a/theater/start_generator.py b/theater/start_generator.py index 97c1059a..4784b291 100644 --- a/theater/start_generator.py +++ b/theater/start_generator.py @@ -1,10 +1,3 @@ -import typing -import random - -import dcs - -from shop import db -from theater.controlpoint import * from theater.base import * from theater.conflicttheater import * @@ -13,7 +6,7 @@ UNIT_AMOUNT_FACTOR = 1 def generate_initial(theater: ConflictTheater, enemy: str): - for cp in theater.enemy_bases(): + for cp in theater.enemy_points(): if cp.captured: continue diff --git a/ui/basemenu.py b/ui/basemenu.py index 39cd5a14..f7460b07 100644 --- a/ui/basemenu.py +++ b/ui/basemenu.py @@ -1,7 +1,3 @@ -from shop import db - -from tkinter import * -from ui.window import * from ui.eventmenu import * from game.game import * diff --git a/ui/eventmenu.py b/ui/eventmenu.py index 1e1827d2..82f9467d 100644 --- a/ui/eventmenu.py +++ b/ui/eventmenu.py @@ -1,10 +1,7 @@ -from tkinter import * -from ui.window import * from ui.eventresultsmenu import * -from shop import db from game.game import * -from game import event +from game import event, db class EventMenu(Menu): @@ -55,7 +52,7 @@ class EventMenu(Menu): row += 1 base = None # type: Base - if self.event.attacker.name == self.game.player: + if self.event.attacker_name == self.game.player: base = self.event.from_cp.base else: base = self.event.to_cp.base diff --git a/ui/eventresultsmenu.py b/ui/eventresultsmenu.py index 35bf9402..a7b5e725 100644 --- a/ui/eventresultsmenu.py +++ b/ui/eventresultsmenu.py @@ -1,13 +1,7 @@ -import math -import itertools - -from tkinter import * from tkinter.ttk import * from ui.window import * -from userdata.debriefing_parser import * from game.game import * -from game import event class EventResultsMenu(Menu): @@ -21,18 +15,22 @@ class EventResultsMenu(Menu): self.event = event self.finished = False + wait_for_debriefing(callback=self.process_debriefing) + def display(self): self.window.clear_right_pane() if not self.finished: Button(self.frame, text="no losses, succ", command=self.simulate_result(0, 1, True)).grid() - Button(self.frame, text="no losses, fail", command=self.simulate_result(0, 1, False)).grid(column=1) + Button(self.frame, text="no losses, fail", command=self.simulate_result(0, 1, False)).grid(row=1, column=1) - Button(self.frame, text="half losses, succ", command=self.simulate_result(0.5, 0.5, True)).grid(row=1, ) - Button(self.frame, text="half losses, fail", command=self.simulate_result(0.5, 0.5, False)).grid(row=1, column=1) + Button(self.frame, text="half losses, succ", command=self.simulate_result(0.5, 0.5, True)).grid(row=2, ) + Button(self.frame, text="half losses, fail", command=self.simulate_result(0.5, 0.5, False)).grid(row=2, column=1) - Button(self.frame, text="full losses, succ", command=self.simulate_result(1, 0, True)).grid(row=2, ) - Button(self.frame, text="full losses, fail", command=self.simulate_result(1, 0, False)).grid(row=2, column=1) + Button(self.frame, text="full losses, succ", command=self.simulate_result(1, 0, True)).grid(row=3, ) + Button(self.frame, text="full losses, fail", command=self.simulate_result(1, 0, False)).grid(row=3, column=1) + + Label(self.frame, text="Play the mission and save debriefing to {}".format(debriefing_directory_location())).grid(row=0, column=0) else: row = 0 if self.event.is_successfull(self.debriefing): @@ -56,6 +54,15 @@ class EventResultsMenu(Menu): Button(self.frame, text="Okay", command=self.dismiss).grid(columnspan=1, row=row); row += 1 + def process_debriefing(self, debriefing: Debriefing): + self.game.finish_event(event=self.event, debriefing=debriefing) + self.game.pass_turn() + + self.finished = True + self.player_losses = debriefing.destroyed_units.get(self.game.player, {}) + self.enemy_losses = debriefing.destroyed_units.get(self.game.enemy, {}) + self.display() + def simulate_result(self, player_factor: float, enemy_factor: float, result: bool): def action(): debriefing = Debriefing() diff --git a/ui/mainmenu.py b/ui/mainmenu.py index 6bc01baf..d2cae4be 100644 --- a/ui/mainmenu.py +++ b/ui/mainmenu.py @@ -1,12 +1,10 @@ -from tkinter import * -from tkinter.ttk import * +import pickle -from ui.window import * -from ui.eventmenu import * from ui.basemenu import * from ui.overviewcanvas import * from game.game import * +from userdata import persistency class MainMenu(Menu): @@ -22,9 +20,10 @@ class MainMenu(Menu): self.frame.grid_columnconfigure(0, weight=1) def display(self): + persistency.save_game(self.game) + self.window.clear_right_pane() self.upd.update() - row = 1 def label(text): diff --git a/ui/overviewcanvas.py b/ui/overviewcanvas.py index 9e1032f4..be58a70d 100644 --- a/ui/overviewcanvas.py +++ b/ui/overviewcanvas.py @@ -79,7 +79,9 @@ class OverviewCanvas: extent=extent) self.canvas.tag_bind(cp_id, "", self.display(cp)) self.create_cp_title((coords[0] + arc_size/2, coords[1] + arc_size/2), cp) - self.canvas.create_text(coords[0], coords[1] - arc_size / 1.5, text="8/4/2", font=("Helvetica", 10)) + + units_title = "{}/{}/{}".format(cp.base.total_planes, cp.base.total_armor, cp.base.total_aa) + self.canvas.create_text(coords[0], coords[1] - arc_size / 1.5, text=units_title, font=("Helvetica", 10)) def display(self, cp: ControlPoint): def action(_): diff --git a/userdata/assets.py b/userdata/assets.py deleted file mode 100644 index bb7b4b83..00000000 --- a/userdata/assets.py +++ /dev/null @@ -1,15 +0,0 @@ -import dcs - -money = 2000 -aircraft = [] -armor = [] -control_points = [] - -def add_aircraft(plane: dcs.planes.PlaneType): - aircraft.append(plane) - -def add_armor(vehicle: dcs.vehicles.Armor): - armor.append(vehicle) - -def add_control_point(cp): - control_points.append(cp) diff --git a/userdata/debriefing.py b/userdata/debriefing.py new file mode 100644 index 00000000..069c4d5c --- /dev/null +++ b/userdata/debriefing.py @@ -0,0 +1,52 @@ +import typing + +import json +import threading +import time +import os + +from datetime import datetime + +DEBRIEFING_LOG_EXTENSION = "log" + + +class Debriefing: + def __init__(self): + self.destroyed_units = {} # type: typing.Dict[str, typing.Dict[str, int]] + + @classmethod + def parse(cls, path: str): + with open(path, "r") as f: + events = json.load(f) + + return Debriefing() + + +def debriefing_directory_location() -> str: + return "build/debriefing" + + +def _logfiles_snapshot() -> typing.Dict[str, float]: + result = {} + for file in os.listdir(debriefing_directory_location()): + fullpath = os.path.join(debriefing_directory_location(), file) + result[file] = os.path.getmtime(fullpath) + + return result + + +def _poll_new_debriefing_log(snapshot: typing.Dict[str, float], callback: typing.Callable): + should_run = True + while should_run: + for file, timestamp in _logfiles_snapshot().items(): + if file not in snapshot or timestamp != snapshot[file]: + callback(Debriefing.parse(os.path.join(debriefing_directory_location(), file))) + should_run = False + break + + time.sleep(1) + + +def wait_for_debriefing(callback: typing.Callable): + threading.Thread(target=_poll_new_debriefing_log, args=(_logfiles_snapshot(), callback)).start() + diff --git a/userdata/debriefing_parser.py b/userdata/debriefing_parser.py deleted file mode 100644 index 3d80aa6a..00000000 --- a/userdata/debriefing_parser.py +++ /dev/null @@ -1,12 +0,0 @@ -import typing -import json - - -class Debriefing: - def __init__(self): - self.destroyed_units = {} # type: typing.Dict[str, typing.Dict[str, int]] - - def parse(self, path: str): - with open(path, "r") as f: - events = json.load(f) - diff --git a/userdata/persistency.py b/userdata/persistency.py new file mode 100644 index 00000000..94144e0b --- /dev/null +++ b/userdata/persistency.py @@ -0,0 +1,38 @@ +import pickle +import os +import shutil + +from game.game import Game + + +def _save_file() -> str: + return "build/save" + + +def _temporary_save_file() -> str: + return "build/save_tmp" + + +def _save_file_exists() -> bool: + return os.path.exists(_save_file()) + + +def restore_game() -> Game: + if not _save_file_exists(): + return None + + try: + with open(_save_file(), "rb") as f: + return pickle.load(f) + except: + return None + + +def save_game(game: Game) -> bool: + try: + with open(_temporary_save_file(), "wb") as f: + pickle.dump(game, f) + shutil.copy(_temporary_save_file(), _save_file()) + return True + except: + return False