diff --git a/__init__.py b/__init__.py index e34e6ab1..16993968 100755 --- a/__init__.py +++ b/__init__.py @@ -25,7 +25,8 @@ theater.kutaisi.base.aircraft = { g = Game(theater=theater) w = ui.window.Window() -m = ui.mainmenu.MainMenu(g, w) +m = ui.mainmenu.MainMenu(w, None, g) +m.display() w.run() diff --git a/game/event.py b/game/event.py index 0eca4455..1313f02c 100644 --- a/game/event.py +++ b/game/event.py @@ -13,6 +13,7 @@ DIFFICULTY_LOG_BASE = 1.5 class Event: silent = False + informational = False operation = None # type: Operation difficulty = 1 # type: int BONUS_BASE = 0 @@ -94,9 +95,9 @@ class InterceptEvent(Event): def commit(self, debriefing: Debriefing): super(InterceptEvent, self).commit(debriefing) if self.is_successfull(debriefing): - self.to_cp.base.affect_strength(0.1 * self.from_cp.captured and -1 or 1) + self.to_cp.base.affect_strength(0.1 * float(self.from_cp.captured and -1 or 1)) else: - self.to_cp.base.affect_strength(0.1 * self.from_cp.captured and 1 or -1) + self.to_cp.base.affect_strength(0.1 * float(self.from_cp.captured and 1 or -1)) def skip(self): if self.to_cp.captured: @@ -182,4 +183,27 @@ class CaptureEvent(Event): attack=armor, intercept=interceptors, defense=self.to_cp.base.armor, - aa=self.to_cp.base.aa) \ No newline at end of file + aa=self.to_cp.base.aa) + + +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): + super(UnitsDeliveryEvent, self).__init__(attacker_name=attacker_name, + defender_name=defender_name, + from_cp=from_cp, + to_cp=to_cp) + + self.units = {} + + def __str__(self): + return "Pending delivery to {}".format(self.to_cp) + + def deliver(self, units: typing.Dict[UnitType, int]): + for k, v in units.items(): + self.units[k] = self.units.get(k, 0) + v + + def skip(self): + self.to_cp.base.commision_units(self.units) diff --git a/game/game.py b/game/game.py index fec7c3c4..f4e24584 100644 --- a/game/game.py +++ b/game/game.py @@ -36,6 +36,7 @@ PLAYER_BUDGET_IMPORTANCE_LOG = 2 class Game: budget = 45 events = None # type: typing.List[Event] + pending_transfers = None # type: typing.Dict[] def __init__(self, theater: ConflictTheater): self.events = [] @@ -55,6 +56,9 @@ class Game: def _generate_enemy_caps(self): for from_cp, to_cp in self.theater.conflicts(False): + if from_cp.base.total_planes == 0 or from_cp.base.total_armor == 0: + continue + if self._roll(ENEMY_CAPTURE_PROBABILITY_BASE, from_cp.base.strength): self.events.append(CaptureEvent(attacker_name=self.enemy, defender_name=self.player, @@ -64,6 +68,9 @@ class Game: def _generate_interceptions(self): for from_cp, to_cp in self.theater.conflicts(False): + if from_cp.base.total_units(FighterSweep) == 0: + continue + if self._roll(ENEMY_INTERCEPT_PROBABILITY_BASE, from_cp.base.strength): self.events.append(InterceptEvent(attacker_name=self.enemy, defender_name=self.player, @@ -100,10 +107,19 @@ class Game: cp.base.commision_units({unit_type: points_to_spend}) def _budget_player(self): - total_importance = sum([x.importance for x in self.theater.player_points()]) - total_strength = sum([x.base.strength for x in self.theater.player_points()]) / len(self.theater.player_points()) + if len(self.theater.player_points()) > 0: + total_importance = sum([x.importance for x in self.theater.player_points()]) + total_strength = sum([x.base.strength for x in self.theater.player_points()]) / len(self.theater.player_points()) - self.budget += math.ceil(math.log(total_importance * total_strength + 1, PLAYER_BUDGET_IMPORTANCE_LOG) * PLAYER_BUDGET_BASE) + self.budget += math.ceil(math.log(total_importance * total_strength + 1, PLAYER_BUDGET_IMPORTANCE_LOG) * PLAYER_BUDGET_BASE) + + def units_delivery_event(self, to_cp: ControlPoint) -> UnitsDeliveryEvent: + event = UnitsDeliveryEvent(attacker_name=self.player, + defender_name=self.player, + from_cp=to_cp, + to_cp=to_cp) + self.events.append(event) + return event def initiate_event(self, event: Event): event.operation.generate() @@ -119,13 +135,14 @@ class Game: def is_player_attack(self, event: Event): return event.attacker.name == self.player - def pass_turn(self): + def pass_turn(self, no_action=False): for event in self.events: event.skip() - self._budget_player() - for cp in self.theater.enemy_bases(): - self._commision_units(cp) + if not no_action: + self._budget_player() + for cp in self.theater.enemy_bases(): + self._commision_units(cp) self.events = [] # type: typing.List[Event] self._fill_cap_events() diff --git a/shop/db.py b/shop/db.py index 46a4bd18..def170cd 100644 --- a/shop/db.py +++ b/shop/db.py @@ -12,34 +12,34 @@ from dcs.unittype import * PRICES = { # planes - Su_25T: 10, - Su_25: 10, - A_10A: 15, + Su_25T: 11, + Su_25: 11, + A_10A: 18, A_10C: 20, - Su_27: 20, - Su_33: 23, - F_15C: 25, - M_2000C: 17, + Su_27: 30, + Su_33: 33, + F_15C: 30, + M_2000C: 11, - MiG_15bis: 10, + MiG_15bis: 6, MiG_21Bis: 13, MiG_29A: 23, - IL_76MD: 20, - S_3B_Tanker: 20, + IL_76MD: 13, + S_3B_Tanker: 13, # armor - Armor.MBT_T_55: 18, - Armor.MBT_T_80U: 20, - Armor.MBT_T_90: 22, + Armor.MBT_T_55: 4, + Armor.MBT_T_80U: 8, + Armor.MBT_T_90: 10, - Armor.MBT_M60A3_Patton: 15, - Armor.MBT_M1A2_Abrams: 20, + Armor.MBT_M60A3_Patton: 6, + Armor.MBT_M1A2_Abrams: 9, - Armor.ATGM_M1134_Stryker: 12, - Armor.APC_BTR_80: 10, + Armor.ATGM_M1134_Stryker: 6, + Armor.APC_BTR_80: 6, } UNIT_BY_TASK = { diff --git a/theater/base.py b/theater/base.py index 8a7011a7..7b9f06e7 100644 --- a/theater/base.py +++ b/theater/base.py @@ -51,7 +51,8 @@ class Base: return itertools.chain(self.aircraft.items(), self.armor.items(), self.aa.items()) def _find_best_unit(self, dict, for_type: Task, count: int) -> typing.Dict: - assert count > 0 + if count <= 0: + return {} sorted_units = [key for key in dict.keys() if key in db.UNIT_BY_TASK[for_type]] sorted_units.sort(key=lambda x: db.PRICES[x], reverse=True) @@ -122,17 +123,19 @@ class Base: def commit_losses(self, units_lost: typing.Dict[typing.Any, int]): for unit_type, count in units_lost.items(): - aircraft_key = next((x for x in self.aircraft.keys() if x.id == unit_type), None) - if aircraft_key: - self.aircraft[aircraft_key] = self.aircraft[aircraft_key] - count + target_array = None + if unit_type in self.aircraft: + target_array = self.aircraft + elif unit_type in self.armor: + target_array = self.armor + elif unit_type in self.aa: + target_array = self.aa + else: + continue - armor_key = next((x for x in self.armor.keys() if x.name == unit_type), None) - if armor_key: - self.armor[armor_key] = self.armor[armor_key] - count - - aa_key = next((x for x in self.aa.keys() if x.name == unit_type), None) - if aa_key: - self.aa[aa_key] = self.aa[aa_key] - count + target_array[unit_type] = target_array[unit_type] - count + if target_array[unit_type] == 0: + del target_array[unit_type] def affect_strength(self, amount): self.strength += amount diff --git a/ui/basemenu.py b/ui/basemenu.py index 88e96687..3e790647 100644 --- a/ui/basemenu.py +++ b/ui/basemenu.py @@ -7,31 +7,16 @@ from ui.eventmenu import * from game.game import * -class BaseMenu: - def __init__(self, window: Window, parent, game: Game, base: Base): - self.window = window +class BaseMenu(Menu): + def __init__(self, window: Window, parent, game: Game, cp: ControlPoint): + super(BaseMenu, self).__init__(window, parent, game) + + self.cp = cp + self.base = cp.base self.frame = window.right_pane - self.parent = parent - self.game = game - self.base = base + self.event = self.game.units_delivery_event(cp) - self.update() - - def go_back(self): - self.parent.update() - - def buy(self, unit_type): - def action(): - price = db.PRICES[unit_type] - if self.game.budget > price: - self.base.commision_units({unit_type: 1}) - self.game.budget -= price - - self.update() - - return action - - def update(self): + def display(self): self.window.clear_right_pane() row = 0 @@ -39,9 +24,13 @@ class BaseMenu: nonlocal row existing_units = self.base.total_units_of_type(unit_type) - Label(self.frame, text=db.unit_type_name(unit_type)).grid(column=0, row=row, sticky=W) - Label(self.frame, text="{}m {}".format(unit_price, existing_units)).grid(column=1, row=row) - Button(self.frame, text="Buy", command=self.buy(unit_type)).grid(column=2, row=row) + scheduled_units = self.event.units.get(unit_type, 0) + + Label(self.frame, text="{}".format(db.unit_type_name(unit_type))).grid(column=0, row=row, sticky=W) + Label(self.frame, text="({})".format(existing_units)).grid(column=1, row=row) + Label(self.frame, text="{}m {}".format(unit_price, scheduled_units and "(bought {})".format(scheduled_units) or "")).grid(column=2, row=row) + Button(self.frame, text="Buy", command=self.buy(unit_type)).grid(column=3, row=row) + Button(self.frame, text="Sell", command=self.sell(unit_type)).grid(column=4, row=row) row += 1 units = { @@ -52,11 +41,31 @@ class BaseMenu: } Label(self.frame, text="Budget: {}m".format(self.game.budget)).grid(column=0, row=row, sticky=W) - Button(self.frame, text="Back", command=self.go_back).grid(column=2, row=row) + Button(self.frame, text="Back", command=self.dismiss).grid(column=2, row=row) row += 1 for task_type, units in units.items(): - Label(self.frame, text="{}".format(db.task_name(task_type))).grid(column=0, row=row, columnspan=3); row += 1 + Label(self.frame, text="{}".format(db.task_name(task_type))).grid(column=0, row=row, columnspan=5); row += 1 for unit_type in units: purchase_row(unit_type, db.PRICES[unit_type]) + def buy(self, unit_type): + def action(): + price = db.PRICES[unit_type] + if self.game.budget >= price: + self.event.deliver({unit_type: 1}) + self.game.budget -= price + + self.display() + + return action + + def sell(self, unit_type): + def action(): + if self.base.total_units_of_type(unit_type) > 0: + price = db.PRICES[unit_type] + self.game.budget += price + self.base.commit_losses({unit_type: 1}) + self.display() + + return action \ No newline at end of file diff --git a/ui/eventmenu.py b/ui/eventmenu.py index 638d5d78..5f75c904 100644 --- a/ui/eventmenu.py +++ b/ui/eventmenu.py @@ -6,22 +6,50 @@ from game.game import * from game import event -class EventMenu: +class EventMenu(Menu): aircraft_scramble_entries = None # type: typing.Dict[PlaneType, Entry] armor_scramble_entries = None # type: typing.Dict[Armor, Entry] def __init__(self, window: Window, parent, game: Game, event: event.Event): - self.window = window - self.frame = self.window.right_pane - self.parent = parent + super(EventMenu, self).__init__(window, parent, game) self.event = event - self.game = game - self.aircraft_scramble_entries = {} self.armor_scramble_entries = {} - self.update() + self.frame = self.window.right_pane + + def display(self): + self.window.clear_right_pane() + row = 0 + + def label(text): + nonlocal row + Label(self.frame, text=text).grid(column=0, row=0) + + row += 1 + + def scrable_row(unit_type, unit_count): + nonlocal row + Label(self.frame, text="{} ({})".format(unit_type.id and unit_type.id or unit_type.name, unit_count)).grid(column=0, row=row) + e = Entry(self.frame) + e.grid(column=1, row=row) + + self.aircraft_scramble_entries[unit_type] = e + row += 1 + + base = None # type: Base + if self.event.attacker.name == self.game.player: + base = self.event.from_cp.base + else: + base = self.event.to_cp.base + + label("Aircraft") + for unit_type, count in base.aircraft.items(): + scrable_row(unit_type, count) + + Button(self.frame, text="Commit", command=self.start).grid(column=0, row=row) + Button(self.frame, text="Back", command=self.dismiss).grid(column=0, row=row) def start(self): scrambled_aircraft = {} @@ -64,35 +92,5 @@ class EventMenu: e.player_attacking(e.to_cp.position.random_point_within(30000), strikegroup=scrambled_aircraft) self.game.initiate_event(self.event) - EventResultsMenu(self.window, self.parent, self.game, self.event) + EventResultsMenu(self.window, self.parent, self.game, self.event).display() - def update(self): - self.window.clear_right_pane() - row = 0 - - def label(text): - nonlocal row - Label(self.frame, text=text).grid(column=0, row=0) - - row += 1 - - def scrable_row(unit_type, unit_count): - nonlocal row - Label(self.frame, text="{} ({})".format(unit_type.id and unit_type.id or unit_type.name, unit_count)).grid(column=0, row=row) - e = Entry(self.frame) - e.grid(column=1, row=row) - - self.aircraft_scramble_entries[unit_type] = e - row += 1 - - base = None # type: Base - if self.event.attacker.name == self.game.player: - base = self.event.from_cp.base - else: - base = self.event.to_cp.base - - label("Aircraft") - for unit, count in base.aircraft.items(): - scrable_row(unit, count) - - Button(self.frame, text="Commit", command=self.start).grid(column=0, row=row) diff --git a/ui/eventresultsmenu.py b/ui/eventresultsmenu.py index 95e6785c..9e406841 100644 --- a/ui/eventresultsmenu.py +++ b/ui/eventresultsmenu.py @@ -1,6 +1,8 @@ import math +import itertools from tkinter import * +from tkinter.ttk import * from ui.window import * from userdata.debriefing_parser import * @@ -8,16 +10,51 @@ from game.game import * from game import event -class EventResultsMenu: +class EventResultsMenu(Menu): + debriefing = None # type: Debriefing + player_losses = {} # type: typing.Dict[UnitType, int] + enemy_losses = {} # type: typing.Dict[UnitType, int] + def __init__(self, window: Window, parent, game: Game, event: Event): - self.window = window + super(EventResultsMenu, self).__init__(window, parent, game) self.frame = window.right_pane - self.parent = parent - - self.game = game self.event = event + self.finished = False - self.update() + 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(row=0, column=0) + Button(self.frame, text="no losses, fail", command=self.simulate_result(0, 1, False)).grid(row=0, column=1) + + Button(self.frame, text="half losses, succ", command=self.simulate_result(0.5, 0.5, True)).grid(row=1, column=0) + 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="full losses, succ", command=self.simulate_result(1, 0, True)).grid(row=2, column=0) + Button(self.frame, text="full losses, fail", command=self.simulate_result(1, 0, False)).grid(row=2, column=1) + else: + row = 0 + if self.event.is_successfull(self.debriefing): + Label(self.frame, text="Operation success").grid(column=0, row=row, columnspan=1); row += 1 + else: + Label(self.frame, text="Operation failed").grid(column=0, row=row, columnspan=1); row += 1 + + Separator(self.frame, orient='horizontal').grid(column=0, row=row, columnspan=1, sticky=NE); row += 1 + Label(self.frame, text="Player losses").grid(row=row, columnspan=1); row += 1 + for unit_type, count in self.player_losses.items(): + Label(self.frame, text=db.unit_type_name(unit_type)).grid(column=0, row=row) + Label(self.frame, text="{}".format(count)).grid(column=1, row=row) + row += 1 + + Separator(self.frame, orient='horizontal').grid(column=0, row=row, columnspan=1, sticky=NE); row += 1 + Label(self.frame, text="Enemy losses").grid(columnspan=1, row=row); row += 1 + for unit_type, count in self.enemy_losses.items(): + Label(self.frame, text=db.unit_type_name(unit_type)).grid(column=0, row=row) + Label(self.frame, text="{}".format(count)).grid(column=1, row=row) + row += 1 + + Button(self.frame, text="Okay", command=self.dismiss).grid(column=0, columnspan=1, row=row); row += 1 def simulate_result(self, player_factor: float, enemy_factor: float, result: bool): def action(): @@ -27,35 +64,24 @@ class EventResultsMenu: result = {} for group in groups: for unit in group.units: - result[unit.type] = result.get(unit.type, 0) + 1 * mult + result[unit.unit_type] = result.get(unit.unit_type, 0) + 1 * mult - return {x: math.floor(y) for x, y in result.items()} + return {x: math.ceil(y) for x, y in result.items() if y >= 1} player_planes = self.event.operation.mission.country(self.game.player).plane_group enemy_planes = self.event.operation.mission.country(self.game.enemy).plane_group - player_losses = count_planes(player_planes, player_factor) - enemy_losses = count_planes(enemy_planes, enemy_factor) + self.player_losses = count_planes(player_planes, player_factor) + self.enemy_losses = count_planes(enemy_planes, enemy_factor) debriefing.destroyed_units = { - self.game.player: player_losses, - self.game.enemy: enemy_losses, + self.game.player: self.player_losses, + self.game.enemy: self.enemy_losses, } + self.finished = True self.game.finish_event(self.event, debriefing) + self.display() self.game.pass_turn() - self.parent.update() return action - - def update(self): - self.window.clear_right_pane() - - Button(self.frame, text="no losses, succ", command=self.simulate_result(0, 1, True)).grid(row=0, column=0) - Button(self.frame, text="no losses, fail", command=self.simulate_result(0, 1, False)).grid(row=0, column=1) - - Button(self.frame, text="half losses, succ", command=self.simulate_result(0.5, 0.5, True)).grid(row=1, column=0) - 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="full losses, succ", command=self.simulate_result(1, 0, True)).grid(row=2, column=0) - Button(self.frame, text="full losses, fail", command=self.simulate_result(1, 0, False)).grid(row=2, column=1) diff --git a/ui/mainmenu.py b/ui/mainmenu.py index 708e5ee8..ea830b68 100644 --- a/ui/mainmenu.py +++ b/ui/mainmenu.py @@ -7,30 +7,19 @@ from ui.basemenu import * from game.game import * -class MainMenu: - def __init__(self, game: Game, window: Window): - self.image = PhotoImage(file="resources/caumap.gif") - self.game = game - self.window = window +class MainMenu(Menu): + def __init__(self, window: Window, parent, game: Game): + super(MainMenu, self).__init__(window, parent, game) + + self.image = PhotoImage(file="resources/caumap.gif") map = Label(window.left_pane, image=self.image) map.grid(column=0, row=0) self.frame = self.window.right_pane self.frame.grid_columnconfigure(0, weight=1) - self.update() - def pass_turn(self): - self.game.pass_turn() - self.update() - - def start_event(self, event) -> typing.Callable: - return lambda: EventMenu(self.window, self, self.game, event) - - def go_cp(self, cp: ControlPoint) -> typing.Callable: - return lambda: BaseMenu(self.window, self, self.game, cp.base) - - def update(self): + def display(self): self.window.clear_right_pane() row = 1 @@ -59,6 +48,14 @@ class MainMenu: label("Budget: {}m".format(self.game.budget)) for event in self.game.events: + if not event.informational: + continue + label(str(event)) + + for event in self.game.events: + if event.informational: + continue + event_button(event, "{} {}".format(event.attacker.name != self.game.player and "!" or " ", event)) Separator(self.frame, orient='horizontal').grid(column=0, row=row, sticky=EW); row += 1 @@ -76,3 +73,12 @@ class MainMenu: Label(self.frame, text=title).grid(column=0, row=row, sticky=NE) row += 1 + def pass_turn(self): + self.game.pass_turn(no_action=True) + self.display() + + def start_event(self, event) -> typing.Callable: + return lambda: EventMenu(self.window, self, self.game, event).display() + + def go_cp(self, cp: ControlPoint) -> typing.Callable: + return lambda: BaseMenu(self.window, self, self.game, cp).display() diff --git a/ui/window.py b/ui/window.py index 8d0e64b9..01c389bb 100644 --- a/ui/window.py +++ b/ui/window.py @@ -1,4 +1,5 @@ from tkinter import * +from game.game import * class Window: @@ -39,3 +40,17 @@ class Window: def run(self): self.tk.mainloop() + + +class Menu: + parent = None # type: Menu + def __init__(self, window: Window, parent, game: Game): + self.window = window + self.parent = parent + self.game = game + + def dismiss(self): + self.parent.display() + + def display(self): + pass