diff --git a/Lower.heic b/Lower.heic deleted file mode 100644 index 898b61f..0000000 Binary files a/Lower.heic and /dev/null differ diff --git a/ai_helpers.py b/ai_helpers.py index d480362..df614e1 100644 --- a/ai_helpers.py +++ b/ai_helpers.py @@ -10,7 +10,8 @@ def pretty_print_view(view, out=sys.stdout, indent=""): """View coming from the simulation - This method will print an indented version of the view (dict of world state) using the + This method will print an indented version of the view (dict of world + state) using the output file specified. By default it sends output to stdout. Users can but in general should not need to pass in a value for indent. """ @@ -41,7 +42,7 @@ def get_sub_view(view, *args): return get_sub_view(subview, *args[1:]) except IndexError: return view - except: + except Exception: return None @@ -193,7 +194,8 @@ def search_radar_for_obj_name(view, name): def get_comp_views_of_vtype(view, vtype): """Gets component views of a vtype - Returns a list of all the views within the comp view with a specific vtype.""" + Returns a list of all the views within the comp view with a specific + vtype.""" views = [] comp_subview = get_sub_view(view, "comp") if comp_subview is not None: diff --git a/comp.py b/comp.py index d3f39ea..445e080 100644 --- a/comp.py +++ b/comp.py @@ -1,8 +1,31 @@ -import math - -import vec2 +import copy import action +CTYPES_LIST = ["FixedGun", "Engine", "Radar", "CnC", "Radio", "Arm"] + +COMP_ATTRS_BY_CTYPE = { + "FixedGun": [ + ("reload_ticks", 1), + ("ammunition", 0), + ("min_damage", 0), + ("max_damage", 0), + ("range", 0), + ], + "Engine": [("min_speed", 0.0), ("max_speed", 0.0), ("max_turnrate", 0.0)], + "Radar": [ + ("range", 0), + ("level", 0), + ("visarc", 0), + ("offset_angle", 0), + ("resolution", 0), + ], + "CnC": [("max_cmds_per_tick", 0)], + "Radio": [ + ("max_range", 0), + ], + "Arm": [("max_bulk", 0), ("max_weight", 0)], +} + class Comp: def __init__(self, data): @@ -12,7 +35,7 @@ def __init__(self, data): Sets command, update, and view_keys functions based on component type """ - self.data = data + self.data = copy.deepcopy(data) self.command = self.no_command self.update = self.no_update @@ -24,14 +47,19 @@ def __init__(self, data): self.command = self.fixed_gun_command self.update = self.fixed_gun_update self.set_view_keys_fixed_gun() + self.data["reload_ticks_remaining"] = 0 + self.data["reloading"] = False elif ctype == "Engine": self.command = self.engine_command self.update = self.engine_update self.set_view_keys_engine() + self.data["cur_speed"] = 0.0 + self.data["cur_turnrate"] = 0.0 elif ctype == "Radar": self.command = self.radar_command self.update = self.radar_update self.set_view_keys_radar() + self.data["active"] = False elif ctype == "CnC": self.command = self.cnc_command self.update = self.cnc_update @@ -40,10 +68,12 @@ def __init__(self, data): self.command = self.radar_command self.update = self.radio_update self.set_view_keys_radio() + self.data["message"] = None elif ctype == "Arm": self.command = self.arm_command self.update = self.arm_update self.set_view_keys_arm() + self.data["item"] = None def get_data(self, key): """Gets data""" @@ -154,7 +184,10 @@ def fixed_gun_command(self, cmd): a.set_type("HIGHSPEED_PROJECTILE") a.add_data("slot_id", self.get_data("slot_id")) a.add_data("compname", self.get_data("name")) - a.add_data("direction", self.get_data("parent").get_data("facing")) + a.add_data( + "direction", + self.get_data("parent").get_data("facing") + ) a.add_data("min_damage", self.get_data("min_damage")) a.add_data("max_damage", self.get_data("max_damage")) a.add_data("range", self.get_data("range")) @@ -214,7 +247,8 @@ def engine_command(self, cmd): if abs(new_turnrate) <= self.get_data("max_turnrate"): self.data["cur_turnrate"] = new_turnrate else: - self.data["cur_turnrate"] = self.get_data("max_turnrate") + self.data["cur_turnrate"] = \ + self.get_data("max_turnrate") return actions @@ -367,7 +401,7 @@ def arm_update(self): return [] ########################################################################### - ## WEAPON RELATED FUNCTIONS + # WEAPON RELATED FUNCTIONS def set_reload_ticks_to_full(self): """Set reload ticks to full""" self.data["reload_ticks_remaining"] = self.data["reload_ticks"] @@ -387,7 +421,7 @@ def update_reloading(self): self.data["reloading"] = False ########################################################################### - ## ENGINE RELATED FUNCTIONS + # ENGINE RELATED FUNCTIONS def is_moving(self): """Determines if engine is moving""" return self.data["cur_speed"] != 0.0 @@ -397,13 +431,13 @@ def is_turning(self): return self.data["cur_turnrate"] != 0.0 ########################################################################### - ## RADAR RELATED FUCNTIONS + # RADAR RELATED FUCNTIONS def is_transmitting(self): """Determines if radar is transmitting""" return self.get_data("active") ########################################################################### - ## ARM RELATED FUNCTIONS + # ARM RELATED FUNCTIONS def is_holding_item(self): """Determines if arm is holding an item""" return self.get_data("item") is not None @@ -411,11 +445,12 @@ def is_holding_item(self): def can_take_item(self, weight, bulk): """Determines if arm can take item""" return ( - self.get_data("max_weight") >= weight and self.get_data("max_bulk") >= bulk + self.get_data("max_weight") >= weight + and self.get_data("max_bulk") >= bulk ) ########################################################################### - ## + # def is_active(self): """Determines if component is active""" return self.get_data("active") diff --git a/gstate.py b/gstate.py index 3f6c493..79237a9 100644 --- a/gstate.py +++ b/gstate.py @@ -5,6 +5,20 @@ # Provides a global repository for state information. +GSTATE_TYPES = [ + "ITEMS_TOUCH", + "OBJ_ITEMS_TOUCH", + "N_OBJS_DESTROYED", + "N_OBJS_IN_LOCS" +] + +GOAL_ATTRS_BY_TYPE = { + "ITEMS_TOUCH": [("items", [])], + "OBJ_ITEMS_TOUCH": [("items", []), ("object", "")], + "N_OBJS_DESTROYED": [("amount", 0), ("objects", [])], + "N_OBJS_IN_LOCS": [("amount", 0), ("objects", []), ("locations", [])], +} + class GState: def __init__(self, data): @@ -20,15 +34,15 @@ def __init__(self, data): self.init_state = None self.check_state = None - if self.data["type"] == "OBJ_ITEMS_TOUCH": + if self.data["type"] == "N_OBJS_IN_LOCS": + self.check_state = self.check_n_objs_in_locs + self.init_state = self.init_n_objs_in_locs + elif self.data["type"] == "OBJ_ITEMS_TOUCH": self.check_state = self.check_obj_items_touch self.init_state = self.init_obj_items_touch elif self.data["type"] == "ITEMS_TOUCH": self.check_state = self.check_item_touch self.init_state = self.init_item_touch - elif self.data["type"] == "ALL_OBJS_DESTROYED": - self.check_state = self.check_all_objs_destroyed - self.init_state = self.init_all_objs_destroyed elif self.data["type"] == "N_OBJS_DESTROYED": self.check_state = self.check_n_objs_destroyed self.init_state = self.init_n_objs_destroyed @@ -37,6 +51,57 @@ def get_data(self, key): """Gets data""" return self.data[key] + ############################################################ + # N_OBJS_IN_LOCS + # This gstate tracks if a specific amount of objects have + # reached a set of locations. + # + # A simple scenario is 1 obj and 1 location: i.e., maze. + # + # Fox-hound scenarios where 1 obj is trying to reach + # one of multiple safe locations and several "foxes" + # are trying to destroy it. + # + # But it also supports teams or parts of teams, multiple + # locations. + def init_n_objs_in_locs(self, objs, items): + """Initializes data for object in a cell + + N of the objects must be in one of the specified cells. + """ + + # Don't need to store items...irrelevant + + for o in objs.values(): + if o.get_data("id") in self.data["objects"]: + self.objs.append(o) + + def check_n_objs_in_locs(self): + amt = self.data["amount"] + + # First check if N objects are still alive. + # Prevents impossible states in which combat is allowed + # and too many objects are destroyed. + num_alive = 0 + for o in self.objs: + if o.get_data("alive"): + num_alive += 1 + if num_alive < amt: + return False + + # Now check if enough objects have reached the dest cells + locations = self.data["locations"] + for o in self.objs: + for loc in locations: + if loc == f"{o.get_data('x')},{o.get_data('y')}": + amt -= 1 + break + + if amt <= 0: + return True + + return False + ############################################################ # OBJ_ITEM_TOUCH @@ -46,94 +111,89 @@ def init_obj_items_touch(self, objs, items): One of the objects must touch all items simultaneously. """ for i in items.values(): - if i.get_data("name") in self.data["items"]: + if i.get_data("id") in self.data["items"]: self.items.append(i) for o in objs.values(): - if o.get_data("name") in self.data["objs"]: + if o.get_data("id") in self.data["objects"]: self.objs.append(o) def check_obj_items_touch(self): """Check if object-item-touch condition has been met""" # First see if all items are in the same location - prev_x = None - prev_y = None - state_x = True - state_y = True + loc = None + all_touching = True for i in self.items: - if prev_x is None: - prev_x = i.get_data("x") - prev_y = i.get_data("y") + if loc is None: + loc = (i.get_data("x"), i.get_data("y")) else: - state_x = state_x and prev_x == i.get_data("x") - state_y = state_y and prev_y == i.get_data("y") + cur_loc = (i.get_data("x"), i.get_data("y")) + all_touching = loc == cur_loc + + # Something isn't in the same location, stop. + if not all_touching: + break # If all items are in the same place... - if state_x and state_y: - # Assume obj is not... - self.data["state"] = False - # If 1 obj is, set state to true + if all_touching: + + # If the obj is, set state to true for o in self.objs: - if prev_x == o.get_data("x") and prev_y == o.get_data("y"): - self.data["state"] = True - else: - self.data["state"] = False + if loc == (o.get_data("x"), o.get_data("y")): + return True - return self.data["state"] + return False ############################################################ # ITEM TOUCH def init_item_touch(self, objs, items): """Initializes data for item-touch end-state""" for i in items.values(): - if i.get_data("name") in self.data["items"]: + if i.get_data("id") in self.data["items"]: self.items.append(i) def check_item_touch(self): """Checks if item-touch condition has been met""" - prev_x = None - prev_y = None - - state_x = True - state_y = True + loc = None + all_touching = True for i in self.items: - if prev_x is None: - prev_x = i.get_data("x") - prev_y = i.get_data("y") + if loc is None: + loc = (i.get_data("x"), i.get_data("y")) else: - state_x = state_x and prev_x == i.get_data("x") - state_y = state_y and prev_y == i.get_data("y") + cur_loc = (i.get_data("x"), i.get_data("y")) + all_touching = loc == cur_loc - self.data["state"] = state_x and state_y + # Something isn't in the same location, stop. + if not all_touching: + break - return self.data["state"] + return all_touching - ############################################################ - # ALL OBJS DESTROYED - def init_all_objs_destroyed(self, objs, items): - """Initializes data for all-objects-destroyed end-state""" - for o in objs.values(): - if o.get_data("name") in self.data["objs"]: - self.objs.append(o) + # ############################################################ + # # ALL OBJS DESTROYED + # def init_all_objs_destroyed(self, objs, items): + # """Initializes data for all-objects-destroyed end-state""" + # for o in objs.values(): + # if o.get_data("name") in self.data["objs"]: + # self.objs.append(o) - def check_all_objs_destroyed(self): - """Checks if all-objects-destroyed condition has been met""" - all_dead = True + # def check_all_objs_destroyed(self): + # """Checks if all-objects-destroyed condition has been met""" - for o in self.objs: - all_dead = all_dead and not o.get_data("alive") - - self.data["state"] = all_dead + # # If we find one alive, return False + # for o in self.objs: + # if o.get_data("alive"): + # return False - return all_dead + # return True ############################################################ # N OBJS DESTROYED def init_n_objs_destroyed(self, objs, items): """Initializes data for n-objects-destroyed end-state""" for o in objs.values(): - if o.get_data("name") in self.data["objs"]: + if o.get_data("id") in self.data["objects"]: self.objs.append(o) def check_n_objs_destroyed(self): @@ -145,6 +205,4 @@ def check_n_objs_destroyed(self): num_dead += 1 # Int cast just in case someone adds as string - self.data["state"] = num_dead >= int(self.data["number"]) - - return self.data["state"] + return num_dead >= int(self.data["amount"]) diff --git a/hpaint.bmp b/hpaint.bmp deleted file mode 100644 index 150e408..0000000 Binary files a/hpaint.bmp and /dev/null differ diff --git a/images/black_box.png b/images/black_box.png new file mode 100644 index 0000000..2b67a45 Binary files /dev/null and b/images/black_box.png differ diff --git a/images/metal_block.png b/images/metal_block.png new file mode 100644 index 0000000..8d86e75 Binary files /dev/null and b/images/metal_block.png differ diff --git a/item.py b/item.py index 87fa5ea..f33bf1f 100644 --- a/item.py +++ b/item.py @@ -1,7 +1,10 @@ +import copy + + class Item: def __init__(self, data): """Initializes item data""" - self.data = data + self.data = copy.deepcopy(data) self.data["x"] = None self.data["y"] = None self.data["owner"] = None diff --git a/line.py b/line.py index ba74259..5087730 100644 --- a/line.py +++ b/line.py @@ -1,5 +1,3 @@ -from vec2 import Vec2 - class LineSeg: """This class is currently depreciated and is not in use""" @@ -16,26 +14,35 @@ def itersects_point(self, point): elif self.v1.get_y() == self.v2.get_y(): return self.v1.get_y() == point.get_y() else: - return (point.get_y() - self.v1.get_y()) / ( - self.v2.get_y() - self.v1.get_y() - ) == (point.get_x() - self.v1.get_x()) / (self.v2.get_x() - self.v1.get_x()) + return (point.get_y() - self.v1.get_y()) \ + / (self.v2.get_y() - self.v1.get_y()) == \ + (point.get_x() - self.v1.get_x()) \ + / (self.v2.get_x() - self.v1.get_x()) def intersects_line_seg(self, ls): """Determines if line intersects another line""" a = ( - (ls.v2.get_x() - ls.v1.get_x()) * (self.v1.get_y() - ls.v1.get_y()) - - (ls.v2.get_y() - ls.v1.get_y()) * (self.v1.get_x() - ls.v1.get_x()) + (ls.v2.get_x() - ls.v1.get_x()) + * (self.v1.get_y() - ls.v1.get_y()) + - (ls.v2.get_y() - ls.v1.get_y()) + * (self.v1.get_x() - ls.v1.get_x()) ) / ( - (ls.v2.get_y() - ls.v1.get_y()) * (self.v2.get_x() - self.v1.get_x()) - - (ls.v2.get_x() - ls.v1.get_x()) * (self.v2.get_y() - self.v1.get_y()) + (ls.v2.get_y() - ls.v1.get_y()) + * (self.v2.get_x() - self.v1.get_x()) + - (ls.v2.get_x() - ls.v1.get_x()) + * (self.v2.get_y() - self.v1.get_y()) ) b = ( - (self.v2.get_x() - self.v1.get_x()) * (self.v1.get_y() - ls.v1.get_y()) - - (self.v2.get_y() - self.v1.get_y()) * (self.v1.get_x() - ls.v1.get_x()) + (self.v2.get_x() - self.v1.get_x()) + * (self.v1.get_y() - ls.v1.get_y()) + - (self.v2.get_y() - self.v1.get_y()) + * (self.v1.get_x() - ls.v1.get_x()) ) / ( - (ls.v2.get_y() - ls.v1.get_y()) * (self.v2.get_x() - self.v1.get_x()) - - (ls.v2.get_x() - ls.v1.get_x()) * (self.v2.get_y() - self.v1.get_y()) + (ls.v2.get_y() - ls.v1.get_y()) + * (self.v2.get_x() - self.v1.get_x()) + - (ls.v2.get_x() - ls.v1.get_x()) + * (self.v2.get_y() - self.v1.get_y()) ) return a >= 0 and a <= 1 and b >= 0 and b <= 1 diff --git a/loader.py b/loader.py index fb2fba3..57eae76 100644 --- a/loader.py +++ b/loader.py @@ -1,17 +1,13 @@ # Loads teams and other stuff import json -import importlib import obj import copy import zmap import item import comp -import vec2 -import team import gstate -import logging class Loader: @@ -25,43 +21,88 @@ def __init__(self, logger=None): self.team_templates = {} self.gstate_templates = {} - self.load_main_config("settings/main.json") - self.load_comp_templates("settings/components.json") - self.load_obj_templates("settings/objects.json") - self.load_item_templates("settings/items.json") - self.load_map_templates("settings/maps.json") - self.load_team_templates("settings/teams.json") - self.load_gstate_templates("settings/state.json") + self.DIRECTORY = "settings" + self.MAIN_JSON_FILENAME = f"{self.DIRECTORY}/main.json" + self.COMPONENTS_JSON_FILENAME = f"{self.DIRECTORY}/components.json" + self.OBJECTS_JSON_FILENAME = f"{self.DIRECTORY}/objects.json" + self.ITEM_JSON_FILENAME = f"{self.DIRECTORY}/items.json" + self.MAP_JSON_FILENAME = f"{self.DIRECTORY}/maps.json" + self.TEAM_JSON_FILENAME = f"{self.DIRECTORY}/teams.json" + self.GSTATE_JSON_FILENAME = f"{self.DIRECTORY}/state.json" + + self.load_main_config() + self.load_comp_templates() + self.load_obj_templates() + self.load_item_templates() + self.load_map_templates() + self.load_team_templates() + self.load_gstate_templates() self.logger = logger ########################################################################## # GSTATE - def load_gstate_templates(self, file_name): + def load_gstate_templates(self): """Loads gstate templates""" - with open(file_name, "r") as f: + with open(self.GSTATE_JSON_FILENAME, "r") as f: json_objs = json.load(f) for k, v in json_objs.items(): - self.gstate_templates[k] = [] - for g in v: - gs = gstate.GState(g) - self.gstate_templates[k].append(gs) + self.gstate_templates[k] = v + # for g in v: + # gs = gstate.GState(g) + # self.gstate_templates[k].append(gs) + + def save_gstate_templates(self): + if len(self.gstate_templates) > 0: + with open(self.GSTATE_JSON_FILENAME, "w") as f: + json.dump(self.gstate_templates, f, indent=4, sort_keys=True) + + def build_gstate(self, _id): + return gstate.GState(self.gstate_templates[_id]) + + def get_gstate_template(self, _id): + return self.gstate_templates[_id] + + def get_gstate_templates(self): + return self.gstate_templates def copy_gstate_template(self, _id): """Produces a deep copy of a gstate template""" try: return copy.deepcopy(self.gstate_templates[_id]) except KeyError: - self.logger.error("LOADER: copyGStateTemplate() KeyError " + str(_id)) + self.logger.error( + "LOADER: copyGStateTemplate() KeyError " + str(_id) + ) ########################################################################## # OBJ - def load_obj_templates(self, file_name): + def load_obj_templates(self): """Loads object templates""" - with open(file_name, "r") as f: + with open(self.OBJECTS_JSON_FILENAME, "r") as f: json_objs = json.load(f) for k, v in json_objs.items(): - self.obj_templates[k] = obj.Object(v) + self.obj_templates[k] = v + + def save_obj_templates(self): + if len(self.obj_templates) > 0: + with open(self.OBJECTS_JSON_FILENAME, "w") as f: + json.dump(self.obj_templates, f, indent=4, sort_keys=True) + + def build_obj(self, _id): + "Builds an object from a template" + return obj.Object(self.obj_templates[_id]) + + def get_obj_template(self, _id): + "Returns the object template specified by the _id" + try: + return self.obj_templates[_id] + except KeyError: + return None + + def get_obj_templates(self): + "Returns all templates" + return self.obj_templates def copy_obj_template(self, _id): """Produces a deep copy of a object template""" @@ -83,40 +124,88 @@ def get_obj_names(self): ########################################################################## # ITEMS - def load_item_templates(self, file_name): + def load_item_templates(self): """Loads item templates""" - with open(file_name, "r") as f: + with open(self.ITEM_JSON_FILENAME, "r") as f: json_objs = json.load(f) for k, v in json_objs.items(): - self.item_templates[k] = item.Item(v) + self.item_templates[k] = v + + def save_item_templates(self): + if len(self.item_templates) > 0: + with open(self.ITEM_JSON_FILENAME, "w") as f: + json.dump(self.item_templates, f, indent=4, sort_keys=True) + + def get_item_template(self, _id): + return self.item_templates[_id] + + def get_item_templates(self): + return self.item_templates + + def build_item(self, _id): + return item.Item(self.item_templates[_id]) def copy_item_template(self, _id): """Produces a deep copy of an item template""" try: return copy.deepcopy(self.item_templates[_id]) except KeyError: - self.logger.error("LOADER: copyItemTemplate() KeyError " + str(_id)) + self.logger.error( + "LOADER: copyItemTemplate() KeyError " + str(_id) + ) ########################################################################## # COMPS - def load_comp_templates(self, file_name): + # TODO: Need to clean up the comp template functions. Some are old + # and should not be used any longer. + + def load_comp_templates(self): """Loads component templates""" - with open(file_name, "r") as f: + with open(self.COMPONENTS_JSON_FILENAME, "r") as f: json_objs = json.load(f) for k, v in json_objs.items(): - self.comp_templates[k] = comp.Comp(v) + self.comp_templates[k] = v + + def save_comp_templates(self): + if len(self.comp_templates) > 0: + with open(self.COMPONENTS_JSON_FILENAME, "w") as f: + json.dump(self.comp_templates, f, indent=4, sort_keys=True) + + def get_comp_template(self, _id): + return self.comp_templates[_id] + + def get_comp_templates(self): + return self.comp_templates + + # def get_comp_name(self, _id): + # return self.comp_templates[_id].get_data("name") + + def build_comp(self, _id): + return comp.Comp(self.comp_templates[_id]) def copy_comp_template(self, _id): """Produces a deep copy of a component template""" try: return copy.deepcopy(self.comp_templates[_id]) except KeyError: - self.logger.error("LOADER: copyCompTemplate() KeyError " + str(_id)) + self.logger.error( + "LOADER: copyCompTemplate() KeyError " + str(_id) + ) def get_comp_ids(self): """Gets component ids""" return list(self.comp_templates.keys()) + def get_comp_ids_of_ctype(self, _ctype): + comp_ids = [] + for _id, _comp in self.comp_templates.items(): + if _comp["ctype"] == _ctype: + comp_ids.append(_id) + return comp_ids + + def get_comp(self, _id): + return self.comp_templates[_id] + def get_comp_names(self): """Gets component names""" comp_names = [] @@ -124,21 +213,45 @@ def get_comp_names(self): comp_names.append(component.get_data("name")) return comp_names + def get_comp_names_of_type(self, _type): + comps = [] + for component in self.comp_templates.values(): + if component.get_data("ctype") == _type: + comps.append(component.get_data("name")) + return comps + def get_comp_types(self): """Gets component types""" - self.comp_types = [] - for self.component in self.comp_templates.values(): - self.comp_types.append(self.component.get_data("ctype")) - return self.comp_types + comp_types = [] + for component in self.comp_templates.values(): + comp_types.append(component.get_data("ctype")) + return comp_types ########################################################################## # MAPS - def load_map_templates(self, file_name): + def load_map_templates(self): """Loads map templates""" - with open(file_name, "r") as f: + with open(self.MAP_JSON_FILENAME, "r") as f: json_objs = json.load(f) for k, v in json_objs.items(): - self.map_templates[k] = zmap.Map(v) + self.map_templates[k] = v + + def save_map_templates(self): + if len(self.map_templates) > 0: + with open(self.MAP_JSON_FILENAME, "w") as f: + json.dump(self.map_templates, f, indent=4, sort_keys=True) + + def build_map(self, _id): + return zmap.Map(self.map_templates[_id]) + + def get_map_template(self, _id): + return self.map_templates[_id] + + def get_map_templates(self): + return self.map_templates + + def delete_map(self, map_id): + del self.map_templates[map_id] def copy_map_template(self, _id): """Produces a deep copy of a map template""" @@ -153,19 +266,35 @@ def get_map_ids(self): ########################################################################## # TEAMS - def load_team_templates(self, file_name): + def load_team_templates(self): """Loads team templates""" - with open(file_name, "r") as f: + with open(self.TEAM_JSON_FILENAME, "r") as f: json_obj = json.load(f) for k, v in json_obj.items(): self.team_templates[k] = v + def save_team_templates(self): + if len(self.team_templates) > 0: + with open(self.TEAM_JSON_FILENAME, "w") as f: + json.dump(self.team_templates, f, indent=4, sort_keys=True) + + def get_team_templates(self): + return self.team_templates + + def get_team_template(self, _id): + return self.team_templates[_id] + + def copy_all_team_templates(self): + return copy.deepcopy(self.team_templates) + def copy_team_template(self, _id): """Produces a deep copy of a team template""" try: return copy.deepcopy(self.team_templates[_id]) except KeyError: - self.logger.error("LOADER: copyTeamTemplate() KeyError " + str(_id)) + self.logger.error( + "LOADER: copyTeamTemplate() KeyError " + str(_id) + ) def get_team_ids(self): """Gets team ids""" @@ -178,11 +307,25 @@ def get_team_names(self): names.append(t["name"]) return names + def update_team_template(self, _id, **kwargs): + try: + _team = self.team_templates[_id] + for k, v in kwargs.items(): + try: + _team[k] = v + except KeyError: + self.logger.error( + f"LOADER: update_team_template() KeyError {k} is an " + + "invalid team attribute." + ) + except KeyError: + self.logger.error(f"LOADER: update_team_template() KeyError {_id}") + ########################################################################## # Mainconfig - def load_main_config(self, file_name): + def load_main_config(self): """Loads main config""" - with open(file_name, "r") as f: + with open(self.MAIN_JSON_FILENAME, "r") as f: self.main_config = json.load(f) def copy_main_config(self): @@ -194,4 +337,4 @@ def get_main_config_data(self, key): try: return self.main_config[key] except KeyError: - self.logger.error("LOADER: getMainConfigData() KeyError " + str(key)) + self.logger.error(f"LOADER: getMainConfigData() KeyError {key}") diff --git a/main.py b/main.py index 1789a56..66a897f 100644 --- a/main.py +++ b/main.py @@ -1,13 +1,17 @@ import tkinter as tk -import sys import logging import ui_setup -import vec2 -import math -import zmap import ui_homepage import ui_about +import ui_team_config +import ui_object_config +import ui_item_config +import ui_map_config +import ui_component_config +import ui_gstate_config +import loader +import sprite_manager # Initialize the log files @@ -16,46 +20,97 @@ class App(tk.Tk): def __init__(self, *args, **kwargs): """Initializes MAIA window - Instances of home page, about page, and set up page frames are stored in array to be raised as needed + Instances of home page, about page, and set up page frames + are stored in array to be raised as needed """ tk.Tk.__init__(self, *args, **kwargs) - logger = logging.getLogger("main") + self.logger = logging.getLogger("main") handler = logging.FileHandler("log/main.log", mode="w") formatter = logging.Formatter("%(name)s - %(message)s") handler.setFormatter(formatter) - logger.addHandler(handler) + self.logger.addHandler(handler) + self.title("MAIA") - self.geometry("1400x700") - self.minsize(width=1400, height=700) + self.screen_width = self.winfo_screenwidth() + self.screen_height = self.winfo_screenheight() + self.geometry(f"{self.screen_width}x{self.screen_height}") + # self.minsize(width=1400, height=700) self.container = tk.Frame(self) self.container.pack(fill=tk.BOTH, expand=True, side=tk.TOP) self.container.grid_rowconfigure(0, weight=1) self.container.grid_columnconfigure(0, weight=1) + self.ldr = loader.Loader(self.logger) + + # initialize the sprite_manager + sprite_manager.init(self.ldr) + self.frames = {} self.frames["home_page"] = ui_homepage.UIHomepage( - master=self.container, controller=self, logger=logger + master=self.container, controller=self, logger=self.logger ) self.frames["setup_page"] = ui_setup.UISetup( - master=self.container, controller=self, logger=logger + master=self.container, controller=self, logger=self.logger ) self.frames["about_page"] = ui_about.ui_about( - master=self.container, controller=self, logger=logger + master=self.container, controller=self, logger=self.logger + ) + self.frames["config_team"] = ui_team_config.UITeamConfig( + master=self.container, + controller=self, + logger=self.logger, + ldr=self.ldr + ) + self.frames["config_object"] = ui_object_config.UIObjectConfig( + master=self.container, + controller=self, + logger=self.logger, + ldr=self.ldr + ) + self.frames["config_map"] = ui_map_config.UIMapConfig( + master=self.container, + controller=self, + logger=self.logger, + ldr=self.ldr + ) + self.frames["config_component"] = \ + ui_component_config.UIComponentConfig( + master=self.container, + controller=self, + logger=self.logger, + ldr=self.ldr + ) + self.frames["config_item"] = ui_item_config.UIItemConfig( + master=self.container, + controller=self, + logger=self.logger, + ldr=self.ldr + ) + self.frames["config_gstate"] = ui_gstate_config.UIGStateConfig( + master=self.container, + controller=self, + logger=self.logger, + ldr=self.ldr ) # the setup page must be placed first to prevent errors self.frames["setup_page"].grid(row=0, column=0, sticky="nsew") self.frames["home_page"].grid(row=0, column=0, sticky="nsew") self.frames["about_page"].grid(row=0, column=0, sticky="nsew") + self.frames["config_team"].grid(row=0, column=0, sticky="nsew") + self.frames["config_object"].grid(row=0, column=0, sticky="nsew") + self.frames["config_map"].grid(row=0, column=0, sticky="nsew") + self.frames["config_component"].grid(row=0, column=0, sticky="nsew") + self.frames["config_item"].grid(row=0, column=0, sticky="nsew") + self.frames["config_gstate"].grid(row=0, column=0, sticky="nsew") self.show_frame("home_page") def show_frame(self, page_name): """Raises frame""" - frame = self.frames[page_name] - frame.tkraise() + self.frames[page_name].tkraise() if __name__ == "__main__": diff --git a/obj.py b/obj.py index e8707d5..70dc99e 100644 --- a/obj.py +++ b/obj.py @@ -1,9 +1,6 @@ -import random import math import logging - -import vec2 -import line +import copy import zfunctions _2PI = 2.0 * math.pi @@ -12,7 +9,7 @@ class Object: def __init__(self, data): """Initializes default data and view_keys""" - self.data = data + self.data = copy.deepcopy(data) self.JSON_keys = list(self.data.keys()) self.data["damage"] = 0.0 self.data["facing"] = 0.0 @@ -147,7 +144,7 @@ def damage_obj(self, amt): new_damage = old_damage + amt self.set_data("damage", new_damage) - self.log_info("Damaged for " + str(amt) + " - Total Damage: " + str(new_damage)) + self.log_info(f"Damaged for {amt} - Total Damage: {new_damage}") points = 0 if self.get_data("points_count"): diff --git a/settings/components.json b/settings/components.json index 7361106..d52c473 100644 --- a/settings/components.json +++ b/settings/components.json @@ -1,79 +1,68 @@ { "0": { - "id": "0", - "name": "Basic Command Module", "ctype": "CnC", - "max_cmds_per_tick": 100 - }, + "id": "0", + "max_cmds_per_tick": 100, + "name": "Basic Command Module" + }, "1000": { - "id": "1000", - "name": "Main Cannon", + "ammunition": 101, "ctype": "FixedGun", - "reload_ticks": 1, - "reload_ticks_remaining": 0, - "reloading": false, - "ammunition": 100, - "min_damage": 100, + "id": "1000", "max_damage": 100, - "range": 20 + "min_damage": 100, + "name": "Main Cannon", + "range": 20, + "reload_ticks": 1 }, "1100": { - "id": "1100", - "name": "Heavy Cannon", - "ctype": "FixedGun", - "reload_ticks": 3, - "reload_ticks_remaining": 0, - "reloading": false, "ammunition": 100, - "min_damage": 250, + "ctype": "FixedGun", + "id": "1100", "max_damage": 250, - "range": 30 + "min_damage": 250, + "name": "Heavy Cannon", + "range": 30, + "reload_ticks": 3 }, "2000": { - "id": "2000", - "name": "Small Engine", "ctype": "Engine", - "min_speed": -1.0, + "id": "2000", "max_speed": 1.0, - "cur_speed": 0.0, "max_turnrate": 90.0, - "cur_turnrate": 0.0 + "min_speed": -1.0, + "name": "Small Engine" }, "2100": { - "id": "2100", - "name": "Medium Engine", "ctype": "Engine", - "min_speed": -1.0, - "max_speed": 2.0, - "cur_speed": 0.0, - "max_turnrate": 45.0, - "cur_turnrate": 0.0 + "id": "2100", + "max_speed": 1.5, + "max_turnrate": 90.0, + "min_speed": -1.5, + "name": "Medium Engine" }, "3000": { - "id": "3000", - "name": "Simple 120/20/10 Radar", "ctype": "Radar", - "active": false, - "range": 20, + "id": "3000", "level": 1, - "visarc": 0, + "name": "Simple 120/20/10 Radar", "offset_angle": 0, - "resolution": 0 + "range": 20, + "resolution": 0, + "visarc": 0 }, "4000": { - "id": "4000", - "name": "Basic Radio", "ctype": "Radio", - "max_range": 100, "cur_range": 100, - "message": null + "id": "4000", + "max_range": 100, + "name": "Basic Radio" }, "5000": { - "id": "5000", - "name": "Robotic Arm", "ctype": "Arm", - "max_weight": 1000, + "id": "5000", "max_bulk": 1000, - "item": null + "max_weight": 1000, + "name": "Robotic Arm" } } \ No newline at end of file diff --git a/settings/items.json b/settings/items.json index c4933af..dc31d5c 100644 --- a/settings/items.json +++ b/settings/items.json @@ -1,47 +1,37 @@ { "0": { - "id":"0", - "name":"blue_flag", - "weight":1, - "bulk":1, - "text":"B", - "fill":"blue", - "sprite_path":"images/blueFlag.png" + "bulk": 1, + "id": "0", + "name": "blue_flag", + "sprite_filename": "blueFlag.png", + "weight": 1 }, "1": { - "id":"1", - "name":"red_flag", - "weight":1, - "bulk":1, - "text":"R", - "fill":"red", - "sprite_path":"images/redFlag.png" + "bulk": 1, + "id": "1", + "name": "red_flag", + "sprite_filename": "redFlag.png", + "weight": 1 }, - "goal": { - "id":"goal", - "name":"goal", - "weight":1000000, - "bulk":100000000, - "text":"G", - "fill":"yellow", - "sprite_path":"images/goal.png" + "bluebase": { + "bulk": 100000000, + "id": "bluebase", + "name": "blue_base", + "sprite_filename": "blueBase.png", + "weight": 1000000 }, - "redbase":{ - "id":"redbase", - "name":"red_base", - "weight":1000000, - "bulk":100000000, - "text":"X", - "fill":"red", - "sprite_path":"images/redBase.png" + "goal": { + "bulk": 100000000, + "id": "goal", + "name": "goal", + "sprite_filename": "goal.png", + "weight": 1000000 }, - "bluebase":{ - "id":"bluebase", - "name":"blue_base", - "weight":1000000, - "bulk":100000000, - "text":"X", - "fill":"blue", - "sprite_path":"images/blueBase.png" + "redbase": { + "bulk": 100000000, + "id": "redbase", + "name": "red_base", + "sprite_filename": "redBase.png", + "weight": 1000000 } } \ No newline at end of file diff --git a/settings/main.json b/settings/main.json index 09f7aa0..405d0fa 100644 --- a/settings/main.json +++ b/settings/main.json @@ -11,5 +11,6 @@ "TURN":5, "MOVE":6, "TRANSMIT_RADAR":7 - } + }, + "sprite_path":"images" } \ No newline at end of file diff --git a/settings/maps.json b/settings/maps.json index 115d291..95b26d2 100644 --- a/settings/maps.json +++ b/settings/maps.json @@ -1,206 +1,551 @@ { - "Map 1": { - "name": "Simple Maze", + "2": { + "desc": "Simple Maze Map\n\n\n\n", "edge_obj_id": "0", - "desc": "Simple Maze Map", - "width": 10, - "height": 10, - "placed_objects": {}, - "placed_items": { - "goal": [ - { - "x": 8, - "y": 1 - } - ] - }, - "sides": { - "Green": { - "starting_locations": [ - [ - 1, - 1 - ] - ], - "facing": 0, - "color": "green" - } - }, - "win_states": [ - "reach_goal" - ] - - }, - "Map 2": { - "name": "Death Match", - "edge_obj_id": "0", - "desc": "Death Match Map", - "width": 15, - "height": 15, - "placed_objects": {}, - "placed_items": {}, - "sides": { - "Zach": { - "starting_locations": [ - [ - 1, - 1 - ], - [ - 13, - 13 - ] - ], - "facing": 0, - "color": "green" - }, - "LADS": { - "starting_locations": [ - [ - 1, - 13 - ], - [ - 13, - 1 - ] - ], - "facing": 0, - "color": "blue" + "height": 32, + "id": "2", + "items": [ + { + "id": "0", + "x": 10, + "y": 10 } - }, - "win_states": [ - "domination" - ] - }, - "Map 3": { - "name": "Death Match w/Walls", - "edge_obj_id": "0", - "desc": "Death Match Map with walls.", - "width": 15, - "height": 15, - "placed_objects": { - "0":[ - {"x":6,"y":7}, - {"x":7,"y":7}, - {"x":8,"y":7}, - {"x":7,"y":6}, - {"x":7,"y":7}, - {"x":7,"y":8}, - {"x":7,"y":1}, - {"x":7,"y":13}, - {"x":1,"y":7}, - {"x":13,"y":7} - ] - }, - "placed_items": {}, - "sides": { - "Zach": { - "starting_locations": [ - [ - 1, - 1 - ], - [ - 13, - 13 - ] - ], - "facing": 0, - "color": "green" - }, - "LADS": { - "starting_locations": [ - [ - 1, - 13 - ], - [ - 13, - 1 - ] - ], - "facing": 0, - "color": "blue" + ], + "name": "Not so Simple Maze", + "objects": [ + { + "id": "0", + "x": 5, + "y": 5 + }, + { + "id": "0", + "x": 20, + "y": 7 + }, + { + "id": "0", + "x": 19, + "y": 7 + }, + { + "id": "0", + "x": 19, + "y": 8 + }, + { + "id": "0", + "x": 20, + "y": 8 + }, + { + "id": "0", + "x": 20, + "y": 9 + }, + { + "id": "0", + "x": 20, + "y": 10 + }, + { + "id": "0", + "x": 20, + "y": 11 + }, + { + "id": "0", + "x": 20, + "y": 12 + }, + { + "id": "0", + "x": 20, + "y": 13 + }, + { + "id": "0", + "x": 20, + "y": 14 + }, + { + "id": "0", + "x": 20, + "y": 18 + }, + { + "id": "0", + "x": 20, + "y": 17 + }, + { + "id": "0", + "x": 20, + "y": 16 + }, + { + "id": "0", + "x": 20, + "y": 15 + }, + { + "id": "0", + "x": 21, + "y": 15 + }, + { + "id": "0", + "x": 22, + "y": 15 + }, + { + "id": "0", + "x": 22, + "y": 14 + }, + { + "id": "0", + "x": 21, + "y": 14 + }, + { + "id": "0", + "x": 21, + "y": 13 + }, + { + "id": "0", + "x": 22, + "y": 13 + }, + { + "id": "0", + "x": 22, + "y": 12 + }, + { + "id": "0", + "x": 21, + "y": 12 + }, + { + "id": "0", + "x": 21, + "y": 11 + }, + { + "id": "0", + "x": 22, + "y": 11 + }, + { + "id": "0", + "x": 23, + "y": 11 + }, + { + "id": "0", + "x": 24, + "y": 11 + }, + { + "id": "0", + "x": 24, + "y": 12 + }, + { + "id": "0", + "x": 24, + "y": 13 + }, + { + "id": "0", + "x": 24, + "y": 14 + }, + { + "id": "0", + "x": 24, + "y": 15 + }, + { + "id": "0", + "x": 24, + "y": 17 + }, + { + "id": "0", + "x": 24, + "y": 16 + }, + { + "id": "0", + "x": 24, + "y": 18 + }, + { + "id": "0", + "x": 24, + "y": 19 + }, + { + "id": "0", + "x": 24, + "y": 20 + }, + { + "id": "0", + "x": 24, + "y": 22 + }, + { + "id": "0", + "x": 24, + "y": 21 + }, + { + "id": "0", + "x": 24, + "y": 23 + }, + { + "id": "0", + "x": 24, + "y": 25 + }, + { + "id": "0", + "x": 24, + "y": 24 + }, + { + "id": "0", + "x": 24, + "y": 26 + }, + { + "id": "0", + "x": 24, + "y": 27 + }, + { + "id": "0", + "x": 24, + "y": 28 + }, + { + "id": "0", + "x": 24, + "y": 29 + }, + { + "id": "0", + "x": 25, + "y": 13 + }, + { + "id": "0", + "x": 26, + "y": 13 + }, + { + "id": "0", + "x": 26, + "y": 14 + }, + { + "id": "0", + "x": 27, + "y": 14 + }, + { + "id": "0", + "x": 27, + "y": 13 + }, + { + "id": "0", + "x": 28, + "y": 13 + }, + { + "id": "0", + "x": 28, + "y": 14 + }, + { + "id": "0", + "x": 29, + "y": 14 + }, + { + "id": "0", + "x": 29, + "y": 13 + }, + { + "id": "0", + "x": 25, + "y": 14 + }, + { + "id": "0", + "x": 9, + "y": 9 + }, + { + "id": "0", + "x": 11, + "y": 9 + }, + { + "id": "0", + "x": 10, + "y": 9 + }, + { + "id": "0", + "x": 12, + "y": 9 + }, + { + "id": "0", + "x": 11, + "y": 13 + }, + { + "id": "0", + "x": 9, + "y": 13 + }, + { + "id": "0", + "x": 7, + "y": 13 + }, + { + "id": "0", + "x": 5, + "y": 13 + }, + { + "id": "0", + "x": 2, + "y": 13 + }, + { + "id": "0", + "x": 3, + "y": 15 + }, + { + "id": "0", + "x": 4, + "y": 15 + }, + { + "id": "0", + "x": 6, + "y": 17 + }, + { + "id": "0", + "x": 8, + "y": 19 + }, + { + "id": "0", + "x": 11, + "y": 21 + }, + { + "id": "0", + "x": 13, + "y": 21 + }, + { + "id": "0", + "x": 16, + "y": 20 + }, + { + "id": "0", + "x": 16, + "y": 18 + }, + { + "id": "0", + "x": 14, + "y": 17 + }, + { + "id": "0", + "x": 11, + "y": 18 + }, + { + "id": "0", + "x": 9, + "y": 21 + }, + { + "id": "0", + "x": 5, + "y": 24 + }, + { + "id": "0", + "x": 4, + "y": 24 + }, + { + "id": "0", + "x": 4, + "y": 20 + }, + { + "id": "0", + "x": 6, + "y": 19 + }, + { + "id": "0", + "x": 8, + "y": 22 + }, + { + "id": "0", + "x": 12, + "y": 25 + }, + { + "id": "0", + "x": 17, + "y": 26 + }, + { + "id": "0", + "x": 20, + "y": 24 + }, + { + "id": "0", + "x": 18, + "y": 21 + }, + { + "id": "0", + "x": 17, + "y": 12 + }, + { + "id": "0", + "x": 16, + "y": 4 + }, + { + "id": "0", + "x": 21, + "y": 4 + }, + { + "id": "0", + "x": 25, + "y": 7 + }, + { + "id": "0", + "x": 26, + "y": 8 + }, + { + "id": "0", + "x": 17, + "y": 8 + }, + { + "id": "0", + "x": 16, + "y": 9 + }, + { + "id": "0", + "x": 14, + "y": 10 + }, + { + "id": "0", + "x": 13, + "y": 8 + }, + { + "id": "0", + "x": 12, + "y": 7 } - }, - "win_states": [ - "domination" - ] - }, - "Map 4": { - "name": "Capture the Flag", - "edge_obj_id": "0", - "desc": "Two teams attempt to capture each other's flag and return to their zone", - "width": 10, - "height": 10, - "placed_objects": {}, - "placed_items": { - "0": [ - { - "x": 6, - "y": 6 - } - ], - "1": [ - { - "x": 3, - "y": 3 - } - ], - "redbase": [ - { - "x": 6, - "y": 8 - }, - { - "x": 7, - "y": 7 - }, - { - "x": 8, - "y": 6 - } - ], - "bluebase": [ - { - "x": 1, - "y": 3 - }, - { - "x": 2, - "y": 2 - }, - { - "x": 3, - "y": 1 - } - ] - }, + ], "sides": { - "Red": { + "0": { + "color": "#5555FF", + "gstate": "blue_vs_red", + "id": "0", + "name": "Blue", + "num_agents": 4, + "random_placement": false, "starting_locations": [ - [ - 1, - 8 - ] - ], - "facing": 1, - "color": "red" - }, - "Blue": { + { + "facing": 0, + "x": 1, + "y": 1 + }, + { + "facing": 0, + "x": 3, + "y": 2 + }, + { + "facing": 0, + "x": 5, + "y": 4 + }, + { + "facing": 0, + "x": 14, + "y": 7 + } + ] + }, + "1": { + "color": "#FF5555", + "gstate": "red_vs_blue", + "id": "1", + "name": "Red", + "num_agents": 4, + "random_placement": false, "starting_locations": [ - [ - 8, - 1 - ] - ], - "facing": 3, - "color": "blue" + { + "facing": 0, + "x": 30, + "y": 30 + }, + { + "facing": 0, + "x": 29, + "y": 29 + }, + { + "facing": 0, + "x": 30, + "y": 29 + }, + { + "facing": 0, + "x": 29, + "y": 30 + } + ] } }, - "win_states": [ - "touchdown" - ] + "width": 32 } } \ No newline at end of file diff --git a/settings/objects.json b/settings/objects.json index cf295f6..59f7d46 100644 --- a/settings/objects.json +++ b/settings/objects.json @@ -1,51 +1,35 @@ { "0": { + "alive_sprite_filename": "metal_block.png", + "character": "X", + "color_alive": "gray1", + "color_dead": "black", + "comp_ids": [], + "dead_sprite_filename": "tombstone.png", + "density": 1000, + "health": 100000000000000000000, "id": "0", - "name" : "Indestructible Block", - "fill_alive":"gray1", - "fill_dead":"black", - "text":"X", - "health" : 100000000000000000000, - "density":1000, - "comp_ids" : [], - "points_count" : false, - "sprite_path" : "images/metal_blocks.png", - "death_sprite_path" : "images/tombstone.png" + "name": "Indestructible Block", + "points": 0 }, "1": { + "alive_sprite_filename": "finalBox.png", + "character": "B", + "color_alive": "tan4", + "color_dead": "black", + "comp_ids": [], + "dead_sprite_filename": "tombstone.png", + "density": "1000", + "health": "10", "id": "1", - "name" : "Box", - "fill_alive":"tan4", - "fill_dead":"black", - "text":"B", - "health" : 10, - "density":1000, - "comp_ids" : [], - "points_count" : false, - "sprite_path" : "images/finalBox.png", - "death_sprite_path" : "images/tombstone.png" - }, - "blue_tank": { - "id": "blue_tank", - "name" : "Blue Tank", - "fill_alive":"blue1", - "fill_dead":"black", - "text":"L", - "health" : 100, - "density":1000, - "comp_ids" : ["0","1000","2000","3000","4000","5000"], - "points_count" : true, - "sprite_path" : "images/blue_right1.png", - "death_sprite_path" : "images/tombstone.png" + "name": "Box", + "points": "0" }, - "red_tank": { - "id": "red_tank", - "name": "Red Tank", - "fill_alive": "red1", - "fill_dead": "black", - "text": "&", - "health": 100, - "density": 1000, + "2": { + "alive_sprite_filename": "blue_right1.png", + "character": "L", + "color_alive": "blue1", + "color_dead": "black", "comp_ids": [ "0", "1000", @@ -54,65 +38,111 @@ "4000", "5000" ], - "points_count": true, - "sprite_path" : "images/red_right1.png", - "death_sprite_path" : "images/tombstone.png" - }, - "player": { - "id": "player", - "name": "player", - "fill_alive": "green", - "fill_dead": "black", - "text": "&", + "dead_sprite_filename": "tombstone.png", + "density": 1000, "health": 100, + "id": "2", + "name": "Blue Tank", + "points": 100 + }, + "3": { + "alive_sprite_filename": "red_right1.png", + "character": "&", + "color_alive": "red1", + "color_dead": "black", + "comp_ids": [ + "0", + "1000", + "2000", + "3000", + "4000", + "5000" + ], + "dead_sprite_filename": "tombstone.png", "density": 1000, + "health": 100, + "id": "3", + "name": "Red Tank", + "points": 100 + }, + "4": { + "alive_sprite_filename": "stick.png", + "character": "&", + "color_alive": "green", + "color_dead": "black", "comp_ids": [ "0", "2000", "3000" ], - "points_count": true, - "sprite_path": "images/stick.png", - "death_sprite_path" : "images/tombstone.png" + "dead_sprite_filename": "tombstone.png", + "density": 1000, + "health": 100, + "id": "4", + "name": "player", + "points": 100 }, - "start_loc": { - "id": "start_loc", - "name": "Starting Location", - "fill_alive": "white", - "fill_dead": "black", - "text": "S", - "health": 0, - "density": 0, + "5": { + "alive_sprite_filename": "wild_flower_13.png", + "character": "S", + "color_alive": "white", + "color_dead": "black", "comp_ids": [], - "points_count": false, - "sprite_path":"images/wild_flower_13.png", - "death_sprite_path" : "images/tombstone.png" + "dead_sprite_filename": "tombstone.png", + "density": 0, + "health": 0, + "id": "5", + "name": "Starting Location", + "points": 0 + }, + "6": { + "alive_sprite_filename": "cliff.png", + "character": "R", + "color_alive": "red", + "color_dead": "black", + "comp_ids": [ + "0", + "1000", + "2000", + "3000", + "4000", + "5000" + ], + "dead_sprite_filename": "tombstone.png", + "density": 1000, + "health": 100, + "id": "6", + "name": "cliff", + "points": 100 }, - "cliff": { - "id":"cliff", - "name":"cliff", - "fill_alive":"red", - "fill_dead":"black", - "text":"R", - "health" : 100, - "density":1000, - "comp_ids" : ["0","1000","2000","3000","4000","5000"], - "points_count" : true, - "sprite_path" : "images/cliff.png", - "death_sprite_path" : "images/tombstone.png" - + "7": { + "alive_sprite_filename": "arrow.png", + "character": "P", + "color_alive": "red", + "color_dead": "black", + "comp_ids": [ + "0", + "2000", + "3000" + ], + "dead_sprite_filename": "tombstone.png", + "density": 1000, + "health": 100, + "id": "7", + "name": "player", + "points": 100 }, - "fastplayer": { - "id": "player", - "name" : "player", - "fill_alive":"red", - "fill_dead":"black", - "text":"P", - "health" : 100, - "density":1000, - "comp_ids" : ["0","2000","3000"], - "points_count" : true, - "sprite_path": "images/arrow.png", - "death_sprite_path" : "images/tombstone.png" + "90": { + "alive_sprite_filename": "", + "character": "O", + "color_alive": "green", + "color_dead": "red", + "comp_ids": [], + "dead_sprite_filename": "", + "density": 0, + "health": 0, + "id": "90", + "name": "None", + "points": 0 } } \ No newline at end of file diff --git a/settings/old_maps.json b/settings/old_maps.json new file mode 100644 index 0000000..115d291 --- /dev/null +++ b/settings/old_maps.json @@ -0,0 +1,206 @@ +{ + "Map 1": { + "name": "Simple Maze", + "edge_obj_id": "0", + "desc": "Simple Maze Map", + "width": 10, + "height": 10, + "placed_objects": {}, + "placed_items": { + "goal": [ + { + "x": 8, + "y": 1 + } + ] + }, + "sides": { + "Green": { + "starting_locations": [ + [ + 1, + 1 + ] + ], + "facing": 0, + "color": "green" + } + }, + "win_states": [ + "reach_goal" + ] + + }, + "Map 2": { + "name": "Death Match", + "edge_obj_id": "0", + "desc": "Death Match Map", + "width": 15, + "height": 15, + "placed_objects": {}, + "placed_items": {}, + "sides": { + "Zach": { + "starting_locations": [ + [ + 1, + 1 + ], + [ + 13, + 13 + ] + ], + "facing": 0, + "color": "green" + }, + "LADS": { + "starting_locations": [ + [ + 1, + 13 + ], + [ + 13, + 1 + ] + ], + "facing": 0, + "color": "blue" + } + }, + "win_states": [ + "domination" + ] + }, + "Map 3": { + "name": "Death Match w/Walls", + "edge_obj_id": "0", + "desc": "Death Match Map with walls.", + "width": 15, + "height": 15, + "placed_objects": { + "0":[ + {"x":6,"y":7}, + {"x":7,"y":7}, + {"x":8,"y":7}, + {"x":7,"y":6}, + {"x":7,"y":7}, + {"x":7,"y":8}, + {"x":7,"y":1}, + {"x":7,"y":13}, + {"x":1,"y":7}, + {"x":13,"y":7} + ] + }, + "placed_items": {}, + "sides": { + "Zach": { + "starting_locations": [ + [ + 1, + 1 + ], + [ + 13, + 13 + ] + ], + "facing": 0, + "color": "green" + }, + "LADS": { + "starting_locations": [ + [ + 1, + 13 + ], + [ + 13, + 1 + ] + ], + "facing": 0, + "color": "blue" + } + }, + "win_states": [ + "domination" + ] + }, + "Map 4": { + "name": "Capture the Flag", + "edge_obj_id": "0", + "desc": "Two teams attempt to capture each other's flag and return to their zone", + "width": 10, + "height": 10, + "placed_objects": {}, + "placed_items": { + "0": [ + { + "x": 6, + "y": 6 + } + ], + "1": [ + { + "x": 3, + "y": 3 + } + ], + "redbase": [ + { + "x": 6, + "y": 8 + }, + { + "x": 7, + "y": 7 + }, + { + "x": 8, + "y": 6 + } + ], + "bluebase": [ + { + "x": 1, + "y": 3 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 3, + "y": 1 + } + ] + }, + "sides": { + "Red": { + "starting_locations": [ + [ + 1, + 8 + ] + ], + "facing": 1, + "color": "red" + }, + "Blue": { + "starting_locations": [ + [ + 8, + 1 + ] + ], + "facing": 3, + "color": "blue" + } + }, + "win_states": [ + "touchdown" + ] + } +} \ No newline at end of file diff --git a/settings/state.json b/settings/state.json index d748f93..c7ccd1a 100644 --- a/settings/state.json +++ b/settings/state.json @@ -1,55 +1,73 @@ { - "touchdown": [ - { - "type": "ITEMS_TOUCH", - "items":["red_flag","red_base"], - "state":false, - "msg": "Red Wins" - }, - { - "type": "ITEMS_TOUCH", - "items":["blue_flag","blue_base"], - "state":false, - "msg": "Blue wins" - } - ], - "reach_goal": [ - { - "type": "OBJ_ITEMS_TOUCH", - "items": ["goal"], - "objs": ["player"], - "state": false, - "msg":"Goal was reached!" - } - ], - "domination": [ - { - "type": "ALL_OBJS_DESTROYED", - "objs": ["Blue Tank"], - "state":false, - "msg": "Red wins" - }, - { - "type": "ALL_OBJS_DESTROYED", - "objs": ["Red Tank"], - "state":false, - "msg":"Blue wins" - } - ], - "destroy_1": [ - { - "type": "N_OBJS_DESTROYED", - "number": 1, - "objs": ["Blue Tank"], - "state":false, - "msg":"Red wins" - }, - { - "type": "N_OBJS_DESTROYED", - "number": 1, - "objs": ["Red Tank"], - "state":false, - "msg": "Blue wins" - } - ] + "blue_vs_red": { + "amount": 1, + "id": "blue_vs_red", + "msg": "Blue wins\n\n", + "objects": [ + "Red Tank" + ], + "type": "N_OBJS_DESTROYED" + }, + "reach_goal": { + "id": "reach_goal", + "items": [ + "goal" + ], + "msg": "Goal was reached!", + "object": "player", + "type": "OBJ_ITEMS_TOUCH" + }, + "reach_home": { + "amount": 1, + "id": "reach_home", + "locations": [ + "9,9", + "8,8", + "9,8" + ], + "msg": "The red tank arrive home.", + "objects": [ + "Red Tank" + ], + "type": "N_OBJS_IN_LOCS" + }, + "reach_origin": { + "amount": 1, + "id": "reach_origin", + "locations": [ + "0,0" + ], + "msg": "You reached the origin. Yay!\n", + "objects": [ + "2:Blue Tank" + ], + "type": "N_OBJS_IN_LOCS" + }, + "red_vs_blue": { + "amount": 1, + "id": "red_vs_blue", + "msg": "Red destroyed one of blue's agents. Red wins.", + "objects": [ + "Blue Tank" + ], + "type": "N_OBJS_DESTROYED" + }, + "touchdown_blue": { + "id": "touchdown_blue", + "items": [ + "blue_flag", + "blue_base" + ], + "msg": "Blue wins", + "type": "ITEMS_TOUCH" + }, + "touchdown_red": { + "id": "touchdown_red", + "items": [ + "red_flag", + "red_base" + ], + "msg": "Red Wins", + "type": "ITEMS_TOUCH" + } } \ No newline at end of file diff --git a/settings/teams.json b/settings/teams.json index 1bf5b0c..c023e43 100644 --- a/settings/teams.json +++ b/settings/teams.json @@ -1,74 +1,158 @@ { - "TeamAlpha": { - "size": 1, - "name": "TeamAlpha", + "AnotherTeam": { "agent_defs": [ { - "callsign": "Leader", - "squad": "A", - "object": "player", - "AI_file": "AI_file1.py" + "AI_file": "Missing", + "callsign": "New Agent 0", + "object": "Missing", + "squad": "Missing" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 2", + "object": "Missing", + "squad": "Missing" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 3", + "object": "Missing", + "squad": "A" } - ] + ], + "name": "AnotherTeam", + "size": "0" }, - "TeamBeta": { - "size": 1, - "name": "TeamBeta", + "TeamAlpha": { "agent_defs": [ { + "AI_file": "AI_file1.py", "callsign": "Leader", - "squad": "B", "object": "player", - "AI_file": "AI_file2.py" + "squad": "A" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 1", + "object": "Missing", + "squad": "Missing" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 2", + "object": "Missing", + "squad": "Missing" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 3", + "object": "Missing", + "squad": "Missing" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 4", + "object": "Missing", + "squad": "Missing" } - ] + ], + "name": "TeamAlpha", + "size": 1 }, - "TeamCharlie": { - "size": 1, - "name": "TeamCharlie", + "TeamBeta": { "agent_defs": [ { - "callsign": "Leader", - "squad": "C", - "object": "player", - "AI_file": "AI_file3.py" + "AI_file": "Missing", + "callsign": "New Agent 0", + "object": "Missing", + "squad": "Missing" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 1", + "object": "Missing", + "squad": "Missing" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 2", + "object": "Missing", + "squad": "Missing" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 3", + "object": "Missing", + "squad": "Missing" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 4", + "object": "Missing", + "squad": "Q" } - ] + ], + "name": "TeamBeta", + "size": 1 }, "TeamDelta": { - "size": 1, - "name": "TeamDelta", "agent_defs": [ { + "AI_file": "AI_file4.py", "callsign": "Leader", - "squad": "D", "object": "player", - "AI_file": "AI_file4.py" + "squad": "D" } - ] + ], + "name": "TeamDelta", + "size": 1 }, - "TeamZach": { - "size": 1, - "name": "TeamZach", + "TeamLADS": { "agent_defs": [ { + "AI_file": "LADS_AI.py", "callsign": "Leader", - "squad": "Z", - "object": "red_tank", - "AI_file": "Zach_AI.py" + "object": "blue_tank", + "squad": "L" } - ] - }, - "TeamLADS": { - "size": 1, + ], "name": "TeamLADS", + "size": 1 + }, + "TeamZach2": { "agent_defs": [ { + "AI_file": "Zach_AI.py", "callsign": "Leader", - "squad": "L", - "object": "blue_tank", - "AI_file": "LADS_AI.py" + "object": "red_tank", + "squad": "Z" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 1", + "object": "Missing", + "squad": "Missing" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 2", + "object": "Missing", + "squad": "Missing" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 3", + "object": "Missing", + "squad": "Missing" + }, + { + "AI_file": "Missing", + "callsign": "New Agent 4", + "object": "Missing", + "squad": "Missing" } - ] + ], + "name": "TeamZach2", + "size": 1 } } \ No newline at end of file diff --git a/sim.py b/sim.py index 515f4d2..4836f83 100644 --- a/sim.py +++ b/sim.py @@ -1,21 +1,12 @@ import importlib import uuid import random - -import copy -import zmap -import comp -import vec2 -import loader import zmath import msgs -from zexceptions import * import valid import views -import gstate import zfunctions - -from ui_sim import UISim +import zexceptions class Sim: @@ -157,11 +148,13 @@ def build_sim(self, ldr): Builds map and set win state(s) """ if not self.has_map(): - raise BuildException("No map was selected.") + raise zexceptions.BuildException("No map was selected.") for k, v in self.sides.items(): if v["teamname"] is None: - raise BuildException("Side " + k + " has no team assignment.") + raise zexceptions.BuildException( + f"Side {k} has no team assignment." + ) # Get the main config dict config = ldr.copy_main_config() @@ -263,7 +256,8 @@ def build_sim(self, ldr): # Load and set AI ai_file_name = agent["AI_file"] ai_spec = importlib.util.spec_from_file_location( - ai_file_name, team_dir + "/" + team_name + "/" + ai_file_name + ai_file_name, + f"{team_dir}/{team_name}/{ai_file_name}" ) ai_module = importlib.util.module_from_spec(ai_spec) ai_spec.loader.exec_module(ai_module) @@ -277,7 +271,7 @@ def build_sim(self, ldr): # Create and store components for c in new_obj.get_data("comp_ids"): - new_comp = ldr.copy_comp_template(c) + new_comp = ldr.build_comp(c) new_comp.set_data("parent", new_obj) new_obj.add_comp(new_comp) @@ -326,26 +320,6 @@ def check_end_of_sim(self): rtn = rtn or r return rtn - # Only 1 team remaining - # teams_remaining = [] - # for name,data in self.sides.items(): - # team_eliminated = True - - # for obj in data['team']['agents'].values(): - # if obj.get_data('alive'): - # team_eliminated=False - # break - # if not team_eliminated: - # teams_remaining.append(name) - - # if len(teams_remaining) == 1: - # self.imsgr.addMsg(msgs.Msg(self.tick,"---GAME OVER---","The winning side is: " + teams_remaining[0])) - # return True - # if len(teams_remaining) == 0: - # self.imsgr.addMsg(msgs.Msg(self.tick,"---GAME OVER---","All teams are dead???")) - # return True - # return False - def get_points_data(self): """Gets points data""" msg = "" @@ -374,7 +348,8 @@ def get_final_scores(self): for agent_name, curr_obj in data["team"]["agents"].items(): agent_score = curr_obj.get_data("points") - team_scores["agents"][curr_obj.get_best_display_name()] = agent_score + team_scores["agents"][curr_obj.get_best_display_name()] = \ + agent_score team_scores["total"] += agent_score final_scores[name] = team_scores @@ -432,7 +407,8 @@ def run_sim(self, turns): for tick in range(self.ticks_per_turn): self.imsgr.add_msg(msgs.Msg(self.tick, "---NEW TICK---", "")) - # Advance turn order sequentially by rotating list of all active agents by 1. + # Advance turn order sequentially by rotating list of all + # active agents by 1. obj_uuids_list[1:] # Check all commands to see if there is @@ -584,7 +560,9 @@ def actn_move(self, curr_obj, actn): if new_x != old_x or new_y != old_y: # Might be moving more than 1 cell. Get trajectory. - cell_path = zmath.get_cells_along_trajectory(x, y, facing, cur_speed) + cell_path = zmath.get_cells_along_trajectory( + x, y, facing, cur_speed + ) cur_cell = (old_x, old_y) collision = False @@ -623,8 +601,8 @@ def actn_move(self, curr_obj, actn): # CRASH INTO SOMETHING # We need to move the obj to the edge - # of the old cell to simulate that they reached the edge of it before - # crashing. + # of the old cell to simulate that they reached the edge of it + # before crashing. if new_x != old_x: if new_x > old_x: curr_obj.set_data("cell_x", 0.99) @@ -663,7 +641,8 @@ def actn_transmit_radar(self, curr_obj, actn): view["slot_id"] = actn.get_data("slot_id") # Set up the necessary data for easy access - radar_facing = curr_obj.get_data("facing") + actn.get_data("offset_angle") + radar_facing = curr_obj.get_data("facing") + \ + actn.get_data("offset_angle") start = radar_facing - actn.get_data("visarc") end = radar_facing + actn.get_data("visarc") angle = start @@ -677,9 +656,12 @@ def actn_transmit_radar(self, curr_obj, actn): # While we're in our arc of visibility while angle <= end: # Get all object/item pings along this angle - pings = self.map.get_all_obj_uuid_along_trajectory(x, y, angle, _range) + pings = self.map.get_all_obj_uuid_along_trajectory( + x, y, angle, _range + ) # Pings should be in order. Start adding if they're not there. - # If the radar's level is less than the obj's density, stop. We can't see through. + # If the radar's level is less than the obj's density, stop. + # We can't see through. # Else keep going. obj_pings = pings["objects"] item_pings = pings["items"] @@ -711,9 +693,8 @@ def actn_transmit_radar(self, curr_obj, actn): temp_view.append(ping) # If our radar level can't penetrate the object, stop. - if actn.get_data("level") < self.objs[ping["uuid"]].get_data( - "density" - ): + if actn.get_data("level") < \ + self.objs[ping["uuid"]].get_data("density"): break for ping in item_pings: @@ -873,7 +854,11 @@ def damage_obj(self, _uuid, damage): # Check for held/stored items held_items_uuids = dead_obj.get_and_remove_all_held_stored_items() for i in held_items_uuids: - self.map.add_item(dead_obj.get_data("x"), dead_obj.get_data("y"), i) + self.map.add_item( + dead_obj.get_data("x"), + dead_obj.get_data("y"), + i + ) # Remove from map self.map.remove_obj( diff --git a/sprite_manager.py b/sprite_manager.py new file mode 100644 index 0000000..e4f3578 --- /dev/null +++ b/sprite_manager.py @@ -0,0 +1,15 @@ +from PIL import Image + +SPRITE_PATH = "" + + +def init(ldr): + global SPRITE_PATH + SPRITE_PATH = ldr.get_main_config_data("sprite_path") + + +def load_image(filename): + try: + return Image.open(f"{SPRITE_PATH}/{filename}") + except Exception: + raise diff --git a/ui_about.py b/ui_about.py index b09c4ba..d18be9b 100644 --- a/ui_about.py +++ b/ui_about.py @@ -1,9 +1,5 @@ import tkinter as tk - -from zexceptions import * -from ui_widgets import * - -from main import * +import ui_widgets as uiw class ui_about(tk.Frame): @@ -24,25 +20,28 @@ def build_ui(self): Sets description text, places description, places label, places home button """ - desc_text = "MAIA is a platform designed for AI competitions that provides a modular 2D \ - simulation environment for which students write AI to control competing agents.\n\ - The goal is to give coders all the tools necessary so that they can focus \ - primarily on analysis of information and decision-making.\n\n\ - MAIA was developed by Dr. Zachary Hutchinson during his graduate studies \ - at the University of Maine, Orono.\n Version 0.22, the most current version of MAIA,\ - was released in October of 2020.\n Further documentation, including overviews of the \ - AI scripts, can be found in the docs directory." + desc_text = "MAIA is a platform designed for AI competitions that " \ + + "provides a modular 2D simulation environment for which " \ + + "students write AI to control competing agents.\n The goal is "\ + + "to give coders all the tools necessary so that they can focus "\ + + "primarily on analysis of information and decision-making.\n\n" \ + + "MAIA was developed by Dr. Zachary Hutchinson during his " \ + + "graduate studies at the University of Maine, Orono.\n" \ + + "Version 0.22, the most current version of MAIA, was " \ + + "released in October of 2020.\n Further documentation, " \ + + "including overviews of the AI scripts, can be found in the " \ + + "docs directory." desc_text = desc_text.replace(" ", "") - self.maia_label = uiLabel(master=self, text="Maine AI Arena") + self.maia_label = uiw.uiLabel(master=self, text="Maine AI Arena") self.maia_label.pack(side=tk.TOP, fill=tk.BOTH, expand=True) - self.description = uiTextbox(master=self, width=60) + self.description = uiw.uiTextbox(master=self, width=60) self.description.insert(1.0, desc_text) self.description.pack(side=tk.TOP, fill=tk.BOTH, expand=True) self.description.config(state="disabled") - self.home_button = uiButton( + self.home_button = uiw.uiButton( master=self, text="Home", command=lambda: self.controller.show_frame("home_page"), diff --git a/ui_advanced_config.py b/ui_advanced_config.py deleted file mode 100644 index 349d43f..0000000 --- a/ui_advanced_config.py +++ /dev/null @@ -1,1961 +0,0 @@ -import tkinter as tk -from tkinter.font import Font -import tkinter.scrolledtext as scrolltext -import queue -import cProfile -import logging -import loader -import json -import comp -import obj -import zmap -from tkinter.messagebox import askyesno -from tkinter.messagebox import showwarning -from tkinter.simpledialog import askstring - -import ui_map_config -from ui_widgets import * - - -class UISettings(tk.Toplevel): - def __init__(self, master=None, logger=None): - super().__init__(master) - self.master = master - self.configure(bg=BGCOLOR) - self.title("MAIA - Advanced Configuration") - - self.geometry("1450x700") - self.minsize(width=1400, height=700) - self.logger = logger - self.ldr = loader.Loader(self.logger) - - self.grid_rowconfigure(0, weight=1) - self.grid_columnconfigure(0, weight=1) - self.grid_columnconfigure(1, weight=1) - self.grid_columnconfigure(2, weight=1) - self.fixed_gun_keys = [ - "reload_ticks", - "reload_ticks_remaining", - "reloading", - "ammunition", - "min_damage", - "max_damage", - "range", - ] - self.engine_keys = [ - "min_speed", - "max_speed", - "cur_speed", - "max_turnrate", - "cur_turnrate", - ] - self.radar_keys = [ - "active", - "range", - "level", - "visarc", - "offset_angle", - "resolution", - ] - self.cnc_keys = ["max_cmds_per_tick"] - self.radio_keys = ["max_range", "cur_range", "message"] - self.arm_keys = ["max_weight", "max_bulk", "item"] - - self.prev_component_combo = None - - self.build_ui() - - self.ui_map = None - - def validate_number_entry(self, input): - """ - Validates each entered value (input) to ensure it is a number. - """ - input.replace(".", "", 1) - input.replace("-", "", 1) - if input.isdigit() or input == "" or "-" in input or "." in input: - return True - - else: - return False - - def get_focused_entry(self): - """ - Returns the currently focused entry in advanced config. - """ - focused_entry = self.focus_get() - return focused_entry - - def build_ui(self): - """ - Initializes all widgets and places them. - """ - # Make main widgets - - self.main_frame = uiQuietFrame(master=self) - self.teams_column = uiQuietFrame(master=self.main_frame) - self.components_column = uiQuietFrame(master=self.main_frame) - self.objects_column = uiQuietFrame(master=self.main_frame) - self.maps_column = uiQuietFrame(master=self.main_frame) - self.title_label = uiLabel(master=self.main_frame, text="Advanced Settings") - - self.validate_num = self.teams_column.register(self.validate_number_entry) - - # Place main widgets - self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) - self.title_label.pack(side=tk.TOP, fill=tk.BOTH, expand=False, padx=10, pady=10) - - # Make Team Widgets - - self.select_team_combo = uiComboBox(master=self.teams_column) - self.select_team_combo.configure(state="readonly") - self.teams_label = uiLabel(master=self.teams_column, text="Teams") - self.team_size_label = uiLabel(master=self.teams_column, text="Size:") - self.team_size_entry = EntryHelp( - master=self.teams_column, - text=( - "The team size field represents how many agents you want in the selected team." - " This field takes numeric values only." - ), - ) - self.team_size_entry.entry.config( - validate="all", validatecommand=(self.validate_num, "%P") - ) - self.team_name_label = uiLabel(master=self.teams_column, text="Name:") - self.team_name_entry = EntryHelp(master=self.teams_column, text="To be added.") - self.agent_frame = uiQuietFrame( - master=self.teams_column, borderwidth=5, relief="ridge", sticky="nsew" - ) - self.callsign_label = uiLabel(master=self.agent_frame, text="Callsign:") - self.callsign_entry = EntryHelp(master=self.agent_frame, text="To be added.") - self.squad_label = uiLabel(master=self.agent_frame, text="Squad:") - self.squad_entry = EntryHelp(master=self.agent_frame, text="To be added.") - self.agent_object_label = uiLabel(master=self.agent_frame, text="Object:") - self.agent_object_entry = EntryHelp( - master=self.agent_frame, text="To be added." - ) - self.ai_file_label = uiLabel(master=self.agent_frame, text="AI File:") - self.ai_file_entry = EntryHelp(master=self.agent_frame, text="To be added.") - self.teams_update_button = uiButton( - master=self.teams_column, command=self.update_teams_json, text="Update" - ) - self.teams_create_button = uiButton( - master=self.teams_column, command=self.create_team, text="Create" - ) - self.teams_delete_button = uiButton( - master=self.teams_column, command=self.delete_team, text="Delete" - ) - - # Place Team Widgets - self.teams_column.pack( - side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10 - ) - self.select_team_combo.grid( - row=1, column=1, columnspan=2, padx=10, pady=10, ipadx=10, ipady=10 - ) - self.teams_label.grid(row=2, column=1, columnspan=2, sticky="nsew") - self.team_size_label.grid(row=3, column=1, sticky="nsew") - self.team_size_entry.frame.grid(row=3, column=2, sticky="nsew") - self.team_name_label.grid(row=4, column=1, sticky="nsew") - self.team_name_entry.frame.grid(row=4, column=2, sticky="nsew") - self.agent_frame.grid( - row=5, column=1, columnspan=2, rowspan=2, sticky="nsew", ipadx=0, ipady=0 - ) - self.callsign_label.grid(row=1, column=1, sticky="nsew") - self.callsign_entry.frame.grid(row=1, column=2, sticky="nsew") - self.squad_label.grid(row=2, column=1, sticky="nsew") - self.squad_entry.frame.grid(row=2, column=2, sticky="nsew") - self.agent_object_label.grid(row=3, column=1, sticky="nsew") - self.agent_object_entry.frame.grid(row=3, column=2, sticky="nsew") - self.ai_file_label.grid(row=4, column=1, sticky="nsew") - self.ai_file_entry.frame.grid(row=4, column=2, sticky="nsew") - self.teams_update_button.grid( - row=7, - column=1, - columnspan=2, - sticky="nsew", - ipadx=2, - ipady=2, - padx=10, - pady=10, - ) - self.teams_create_button.grid( - row=8, - column=1, - columnspan=2, - sticky="nsew", - ipadx=2, - ipady=2, - padx=10, - pady=10, - ) - self.teams_delete_button.grid( - row=9, - column=1, - columnspan=2, - sticky="nsew", - ipadx=2, - ipady=2, - padx=10, - pady=10, - ) - - # Make Component Widgets - self.select_component_combo = uiComboBox(master=self.components_column) - self.select_component_combo.configure(state="readonly") - self.components_label = uiLabel( - master=self.components_column, text="Components" - ) - self.components_update_button = uiButton( - master=self.components_column, - command=self.update_components_json, - text="Update", - ) - self.components_create_button = uiButton( - master=self.components_column, command=self.create_component, text="Create" - ) - self.components_delete_button = uiButton( - master=self.components_column, command=self.delete_components, text="Delete" - ) - self.components_id_label = uiLabel(master=self.components_column, text="ID:") - self.components_id_entry = EntryHelp( - master=self.components_column, text="To be added." - ) - self.components_name_label = uiLabel( - master=self.components_column, text="Name:" - ) - self.components_name_entry = EntryHelp( - master=self.components_column, text="To be added." - ) - self.components_ctype_label = uiLabel( - master=self.components_column, text="CType:" - ) - self.components_type_label = uiLabel(master=self.components_column, text="") - self.components_type_combo = uiComboBox(master=self.components_column) - self.components_type_attr1_label = uiLabel( - master=self.components_column, text="" - ) - self.components_type_attr1_entry = EntryHelp( - master=self.components_column, text="To be added." - ) - self.components_type_attr2_label = uiLabel( - master=self.components_column, text="" - ) - self.components_type_attr2_entry = EntryHelp( - master=self.components_column, text="To be added." - ) - self.components_type_attr3_label = uiLabel( - master=self.components_column, text="" - ) - self.components_type_attr3_entry = EntryHelp( - master=self.components_column, text="To be added." - ) - self.components_type_attr4_label = uiLabel( - master=self.components_column, text="" - ) - self.components_type_attr4_entry = EntryHelp( - master=self.components_column, text="To be added." - ) - self.components_type_attr5_label = uiLabel( - master=self.components_column, text="" - ) - self.components_type_attr5_entry = EntryHelp( - master=self.components_column, text="To be added." - ) - self.components_type_attr6_label = uiLabel( - master=self.components_column, text="" - ) - self.components_type_attr6_entry = EntryHelp( - master=self.components_column, text="To be added." - ) - self.components_type_attr7_label = uiLabel( - master=self.components_column, text="" - ) - self.components_type_attr7_entry = EntryHelp( - master=self.components_column, text="To be added." - ) - - # Place Component Widgets - self.components_column.pack( - side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10 - ) - self.select_component_combo.grid( - row=1, column=1, columnspan=2, padx=10, pady=10, ipadx=10, ipady=10 - ) - self.components_label.grid(row=2, column=1, columnspan=2, sticky="nsew") - self.components_id_label.grid(row=3, column=1, sticky="nsew") - self.components_id_entry.frame.grid(row=3, column=2, sticky="nsew") - self.components_name_label.grid(row=4, column=1, sticky="nsew") - self.components_name_entry.frame.grid(row=4, column=2, sticky="nsew") - self.components_ctype_label.grid(row=5, column=1, sticky="nsew") - self.components_type_label.grid(row=5, column=2, sticky="nsew") - - self.components_type_attr1_label.grid(row=6, column=1, sticky="nsew") - self.components_type_attr1_entry.frame.grid(row=6, column=2, sticky="nsew") - self.components_type_attr1_entry.entry.config( - validate="key", validatecommand=(self.validate_num, "%P") - ) - self.components_type_attr2_label.grid(row=7, column=1, sticky="nsew") - self.components_type_attr2_entry.frame.grid(row=7, column=2, sticky="nsew") - self.components_type_attr2_entry.entry.config( - validate="key", validatecommand=(self.validate_num, "%P") - ) - self.components_type_attr3_label.grid(row=8, column=1, sticky="nsew") - self.components_type_attr3_entry.frame.grid(row=8, column=2, sticky="nsew") - self.components_type_attr3_entry.entry.config( - validate="all", validatecommand=(self.validate_num, "%P") - ) - self.components_type_attr4_label.grid(row=9, column=1, sticky="nsew") - self.components_type_attr4_entry.frame.grid(row=9, column=2, sticky="nsew") - self.components_type_attr4_entry.entry.config( - validate="all", validatecommand=(self.validate_num, "%P") - ) - self.components_type_attr5_label.grid(row=10, column=1, sticky="nsew") - self.components_type_attr5_entry.frame.grid(row=10, column=2, sticky="nsew") - self.components_type_attr5_entry.entry.config( - validate="all", validatecommand=(self.validate_num, "%P") - ) - self.components_type_attr6_label.grid(row=11, column=1, sticky="nsew") - self.components_type_attr6_entry.frame.grid(row=11, column=2, sticky="nsew") - self.components_type_attr6_entry.entry.config( - validate="all", validatecommand=(self.validate_num, "%P") - ) - self.components_type_attr7_label.grid(row=12, column=1, sticky="nsew") - self.components_type_attr7_entry.frame.grid(row=12, column=2, sticky="nsew") - self.components_type_attr7_entry.entry.config( - validate="all", validatecommand=(self.validate_num, "%P") - ) - self.components_type_combo.grid(row=13, column=1, columnspan=2, sticky="nsew") - self.components_update_button.grid( - row=14, - column=1, - columnspan=2, - sticky="nsew", - ipadx=2, - ipady=2, - padx=10, - pady=10, - ) - self.components_create_button.grid( - row=15, - column=1, - columnspan=2, - sticky="nsew", - ipadx=2, - ipady=2, - padx=10, - pady=10, - ) - self.components_delete_button.grid( - row=16, - column=1, - columnspan=2, - sticky="nsew", - ipadx=2, - ipady=2, - padx=10, - pady=10, - ) - - # Make Object Widgets - self.select_objects_combo = uiComboBox(master=self.objects_column) - self.select_objects_combo.configure(state="readonly") - self.objects_label = uiLabel(master=self.objects_column, text="Objects") - self.objects_update_button = uiButton( - master=self.objects_column, command=self.update_objects_json, text="Update" - ) - self.objects_create_button = uiButton( - master=self.objects_column, command=self.create_object, text="Create" - ) - self.objects_delete_button = uiButton( - master=self.objects_column, command=self.delete_object, text="Delete" - ) - self.objects_id_label = uiLabel(master=self.objects_column, text="ID:") - self.objects_id_entry = EntryHelp( - master=self.objects_column, text="To be added." - ) - self.objects_name_label = uiLabel(master=self.objects_column, text="Name:") - self.objects_name_entry = EntryHelp( - master=self.objects_column, text="To be added." - ) - self.objects_fill_alive_label = uiLabel( - master=self.objects_column, text="Fill Alive:" - ) - self.objects_fill_alive_entry = EntryHelp( - master=self.objects_column, text="To be added." - ) - self.objects_fill_dead_label = uiLabel( - master=self.objects_column, text="Fill Dead:" - ) - self.objects_fill_dead_entry = EntryHelp( - master=self.objects_column, text="To be added." - ) - self.objects_text_label = uiLabel(master=self.objects_column, text="Text:") - self.objects_text_entry = EntryHelp( - master=self.objects_column, text="To be added." - ) - self.objects_health_label = uiLabel(master=self.objects_column, text="Health:") - self.objects_health_entry = EntryHelp( - master=self.objects_column, text="To be added." - ) - self.objects_health_entry.entry.config( - validate="all", validatecommand=(self.validate_num, "%P") - ) - self.objects_density_label = uiLabel( - master=self.objects_column, text="Density:" - ) - self.objects_density_entry = EntryHelp( - master=self.objects_column, text="To be added." - ) - self.objects_density_entry.entry.config( - validate="all", validatecommand=(self.validate_num, "%P") - ) - self.objects_comp_ids_label = uiLabel( - master=self.objects_column, text="Comp IDs:" - ) - self.objects_comp_ids_combo = ComboBoxHelp( - master=self.objects_column, text="To be Added." - ) - self.objects_points_count_label = uiLabel( - master=self.objects_column, text="Points Count:" - ) - self.objects_points_count_combo = ComboBoxHelp( - master=self.objects_column, text="To be Added." - ) - - # Place Object Widgets - self.objects_column.pack( - side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10 - ) - self.select_objects_combo.grid( - row=1, column=1, columnspan=2, padx=10, pady=10, ipadx=10, ipady=10 - ) - self.objects_label.grid(row=2, column=1, columnspan=2, sticky="nsew") - self.objects_id_label.grid(row=3, column=1, sticky="nsew") - self.objects_id_entry.frame.grid(row=3, column=2, sticky="nsew") - self.objects_name_label.grid(row=4, column=1, sticky="nsew") - self.objects_name_entry.frame.grid(row=4, column=2, sticky="nsew") - self.objects_fill_alive_label.grid(row=5, column=1, sticky="nsew") - self.objects_fill_alive_entry.frame.grid(row=5, column=2, sticky="nsew") - self.objects_fill_dead_label.grid(row=6, column=1, sticky="nsew") - self.objects_fill_dead_entry.frame.grid(row=6, column=2, sticky="nsew") - self.objects_text_label.grid(row=7, column=1, sticky="nsew") - self.objects_text_entry.frame.grid(row=7, column=2, sticky="nsew") - self.objects_health_label.grid(row=8, column=1, sticky="nsew") - self.objects_health_entry.frame.grid(row=8, column=2, sticky="nsew") - self.objects_density_label.grid(row=9, column=1, sticky="nsew") - self.objects_density_entry.frame.grid(row=9, column=2, sticky="nsew") - self.objects_comp_ids_label.grid(row=10, column=1, sticky="nsew") - self.objects_comp_ids_combo.frame.grid(row=10, column=2, sticky="nsew") - self.objects_points_count_label.grid(row=11, column=1, sticky="nsew") - self.objects_points_count_combo.frame.grid(row=11, column=2, sticky="nsew") - self.objects_update_button.grid( - row=12, - column=1, - columnspan=2, - sticky="nsew", - ipadx=2, - ipady=2, - padx=10, - pady=10, - ) - self.objects_create_button.grid( - row=13, - column=1, - columnspan=2, - sticky="nsew", - ipadx=2, - ipady=2, - padx=10, - pady=10, - ) - self.objects_delete_button.grid( - row=14, - column=1, - columnspan=2, - sticky="nsew", - ipadx=2, - ipady=2, - padx=10, - pady=10, - ) - - # Make Map Widgets - self.select_maps_combo = uiComboBox(master=self.maps_column) - self.select_maps_combo.configure(state="readonly") - self.maps_label = uiLabel(master=self.maps_column, text="Maps") - self.maps_show_button = uiButton( - master=self.maps_column, command=self.show_map, text="Show" - ) - self.maps_update_button = uiButton( - master=self.maps_column, command=self.update_maps_json, text="Update" - ) - self.maps_create_button = uiButton( - master=self.maps_column, command=self.create_map, text="Create" - ) - self.maps_delete_button = uiButton( - master=self.maps_column, command=self.delete_map, text="Delete" - ) - self.maps_id_label = uiLabel(master=self.maps_column, text="ID:") - self.maps_id_entry = EntryHelp(master=self.maps_column, text="To be added.") - self.maps_name_label = uiLabel(master=self.maps_column, text="Name:") - self.maps_name_entry = EntryHelp(master=self.maps_column, text="To be added.") - self.maps_edge_obj_id_label = uiLabel( - master=self.maps_column, text="Edge Object ID:" - ) - self.maps_edge_obj_id_entry = EntryHelp( - master=self.maps_column, text="To be added." - ) - self.maps_desc_label = uiLabel(master=self.maps_column, text="Desc:") - self.maps_desc_entry = EntryHelp(master=self.maps_column, text="To be added.") - self.maps_width_label = uiLabel(master=self.maps_column, text="Width:") - self.maps_width_entry = EntryHelp(master=self.maps_column, text="To be added.") - self.maps_height_label = uiLabel(master=self.maps_column, text="Height:") - self.maps_height_entry = EntryHelp(master=self.maps_column, text="To be added.") - - # Place Map Widgets - self.maps_column.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10) - self.select_maps_combo.grid( - row=1, column=1, columnspan=2, padx=10, pady=10, ipadx=10, ipady=10 - ) - self.maps_label.grid(row=2, column=1, columnspan=2, sticky="nsew") - self.maps_id_label.grid(row=3, column=1, sticky="nsew") - self.maps_id_entry.frame.grid(row=3, column=2, sticky="nsew") - self.maps_name_label.grid(row=4, column=1, sticky="nsew") - self.maps_name_entry.frame.grid(row=4, column=2, sticky="nsew") - self.maps_edge_obj_id_label.grid(row=5, column=1, sticky="nsew") - self.maps_edge_obj_id_entry.frame.grid(row=5, column=2, sticky="nsew") - self.maps_desc_label.grid(row=6, column=1, sticky="nsew") - self.maps_desc_entry.frame.grid(row=6, column=2, sticky="nsew") - self.maps_width_label.grid(row=7, column=1, sticky="nsew") - self.maps_width_entry.frame.grid(row=7, column=2, sticky="nsew") - self.maps_width_entry.entry.config( - validate="key", validatecommand=(self.validate_num, "%P") - ) - self.maps_height_label.grid(row=8, column=1, sticky="nsew") - self.maps_height_entry.frame.grid(row=8, column=2, sticky="nsew") - self.maps_height_entry.entry.config( - validate="key", validatecommand=(self.validate_num, "%P") - ) - self.maps_show_button.grid( - row=9, - column=1, - columnspan=2, - sticky="nsew", - ipadx=2, - ipady=2, - padx=10, - pady=10, - ) - self.maps_update_button.grid( - row=10, - column=1, - columnspan=2, - sticky="nsew", - ipadx=2, - ipady=2, - padx=10, - pady=10, - ) - self.maps_create_button.grid( - row=11, - column=1, - columnspan=2, - sticky="nsew", - ipadx=2, - ipady=2, - padx=10, - pady=10, - ) - self.maps_delete_button.grid( - row=12, - column=1, - columnspan=2, - sticky="nsew", - ipadx=2, - ipady=2, - padx=10, - pady=10, - ) - - self.init_entry_widgets() - - def init_entry_widgets(self): - """ - Gets information from the loader and assigns current values for each setting type. - """ - # TEAM - self.team_data = self.ldr.team_templates - self.team_names = self.ldr.get_team_names() - self.current_team_data = self.team_data[self.team_names[0]] - - self.select_team_combo.configure(values=self.team_names) - self.select_team_combo.current(0) - self.select_team_combo.bind( - "<>", self.change_team_entry_widgets - ) - self.select_team_combo.bind("", self.get_previous_team_combo) - self.show_team_entry(self.current_team_data) - - # COMPONENT - self.component_data = self.ldr.comp_templates - self.component_ids = self.ldr.get_comp_ids() - self.component_names = self.ldr.get_comp_names() - for i in range(len(self.component_ids)): - self.component_ids[i] = ( - self.component_ids[i] + ": " + self.component_names[i] - ) - self.component_types = self.ldr.get_comp_types() - self.current_component_data = self.component_data[ - self.component_ids[0].split(":")[0] - ] - self.component_type_attr = self.current_component_data.view_keys - - self.select_component_combo.configure(values=self.component_ids) - self.select_component_combo.current(0) - self.select_component_combo.bind( - "<>", self.change_components_entry_widgets - ) - self.select_component_combo.bind("", self.get_previous_component_combo) - - self.show_component_entries(self.current_component_data) - - # OBJECT - self.object_data = self.ldr.obj_templates - self.object_ids = self.ldr.get_obj_ids() - self.current_object_data = self.object_data[self.object_ids[0]] - self.object_names = self.ldr.get_obj_names() - for i in range(len(self.object_ids)): - self.object_ids[i] = self.object_ids[i] + ": " + self.object_names[i] - self.current_object_data = self.object_data[self.object_ids[0].split(":")[0]] - self.select_objects_combo.configure(values=self.object_ids) - self.select_objects_combo.current(0) - self.select_objects_combo.bind( - "<>", self.change_objects_entry_widgets - ) - self.select_objects_combo.bind("", self.get_previous_object_combo) - - self.show_object_entry(self.current_object_data) - - # MAP - self.map_data = self.ldr.map_templates - self.map_ids = self.ldr.get_map_ids() - self.current_map_data = self.map_data[self.map_ids[0]] - - self.select_maps_combo.configure(values=self.map_ids) - self.select_maps_combo.current(0) - self.select_maps_combo.bind( - "<>", self.change_maps_entry_widgets - ) - self.select_maps_combo.bind("", self.get_previous_map_combo) - - self.show_map_entry(self.current_map_data) - - def get_previous_component_combo(self, event): - self.prev_component_combo = self.select_component_combo.current() - - def get_previous_object_combo(self, event): - self.prev_object_combo = self.select_objects_combo.current() - - def get_previous_map_combo(self, event): - self.prev_map_combo = self.select_maps_combo.current() - - def get_previous_team_combo(self, event): - self.prev_team_combo = self.select_team_combo.current() - - def change_team_entry_widgets(self, event=None): - """ - Gets the correct team data for the currently selected team. - """ - # the answer variable defaults to true - self.answer = True - self.get_focused_entry() - # if any of the team entry values differ from their starting values, - # the user is warned that they could be overwritten - if not ( - ( - (self.team_name_entry.entry.get() == self.current_team_data["name"]) - and ( - self.team_size_entry.entry.get() - == str(self.current_team_data["size"]) - ) - and ( - self.callsign_entry.entry.get() - == self.current_team_data["agent_defs"][0]["callsign"] - ) - and ( - self.squad_entry.entry.get() - == self.current_team_data["agent_defs"][0]["squad"] - ) - and ( - self.agent_object_entry.entry.get() - == self.current_team_data["agent_defs"][0]["object"] - ) - and ( - self.ai_file_entry.entry.get() - == self.current_team_data["agent_defs"][0]["AI_file"] - ) - ) - ): - self.answer = askyesno( - title="confirmation", - message="""Warning: You have modified Team values and have not Updated. - Your changes will not be saved. Are you sure you would like continue?""", - ) - - # the current team is successfully changed if the user made no changes, - # or if the user confirms they are fine with their changes being overwritten - if self.answer: - # currentTeamIdx = self.selectTeamCombo.current() - self.current_team_data = self.team_data[self.select_team_combo.get()] - self.show_team_entry(self.current_team_data) - else: - self.select_team_combo.current(self.prev_team_combo) - - def change_components_entry_widgets(self, event=None): - """ - Gets the correct component data for the currently selected team. - """ - self.answer = True - - if self.current_component_data.get_data("ctype") == "CnC": - ctype_attributes = self.cnc_keys - elif self.current_component_data.get_data("ctype") == "FixedGun": - ctype_attributes = self.fixed_gun_keys - elif self.current_component_data.get_data("ctype") == "Engine": - ctype_attributes = self.engine_keys - elif self.current_component_data.get_data("ctype") == "Radar": - ctype_attributes = self.radar_keys - elif self.current_component_data.get_data("ctype") == "Radio": - ctype_attributes = self.radio_keys - elif self.current_component_data.get_data("ctype") == "Arm": - ctype_attributes = self.arm_keys - - if not ( - ( - ( - self.components_id_entry.entry.get() - == self.current_component_data.get_data("id") - ) - and ( - self.components_name_entry.entry.get() - == self.current_component_data.get_data("name") - ) - and ( - self.components_type_combo.get() - == self.current_component_data.get_data("ctype") - ) - and ( - self.components_type_attr1_entry.entry.get() - == str(self.current_component_data.get_data(ctype_attributes[0])) - ) - and ( - (len(ctype_attributes) < 2) - or ( - self.components_type_attr2_entry.entry.get() - == str( - self.current_component_data.get_data(ctype_attributes[1]) - ) - ) - ) - and ( - (len(ctype_attributes) < 3) - or self.current_component_data.get_data(ctype_attributes[2]) is None - or ( - self.components_type_attr3_entry.entry.get() - == str( - self.current_component_data.get_data(ctype_attributes[2]) - ) - ) - ) - and ( - (len(ctype_attributes) < 4) - or ( - self.components_type_attr4_entry.entry.get() - == str( - self.current_component_data.get_data(ctype_attributes[3]) - ) - ) - ) - and ( - (len(ctype_attributes) < 5) - or ( - self.components_type_attr5_entry.entry.get() - == str( - self.current_component_data.get_data(ctype_attributes[4]) - ) - ) - ) - and ( - (len(ctype_attributes) < 6) - or ( - self.components_type_attr6_entry.entry.get() - == str( - self.current_component_data.get_data(ctype_attributes[5]) - ) - ) - ) - and ( - (len(ctype_attributes) < 7) - or ( - self.components_type_attr7_entry.entry.get() - == str( - self.current_component_data.get_data(ctype_attributes[6]) - ) - ) - ) - ) - ): - self.answer = askyesno( - title="confirmation", - message="""Warning: You have modified Component values and have not Updated. - Your changes will not be saved. Are you sure you would like continue?""", - ) - - if self.answer is True: - current_component_idx = self.select_component_combo.current() - self.component_type_attr = self.component_data[ - self.component_ids[current_component_idx].split(":")[0] - ].view_keys - self.current_component_data = self.component_data[ - self.component_ids[current_component_idx].split(":")[0] - ] - self.show_component_entries(self.current_component_data) - else: - self.select_component_combo.current(self.prev_component_combo) - - def change_objects_entry_widgets(self, event=None): - """ - Gets the correct object data for the currently selected team. - """ - self.answer = True - comp_ids = self.current_comp_ids - if len(comp_ids) != 0: - if comp_ids[-1] == "Add New Comp ID": - comp_ids.pop(-1) - if not ( - ( - ( - self.objects_id_entry.entry.get() - == self.current_object_data.get_data("id") - ) - and ( - self.objects_name_entry.entry.get() - == self.current_object_data.get_data("name") - ) - and ( - self.objects_fill_alive_entry.entry.get() - == self.current_object_data.get_data("fill_alive") - ) - and ( - self.objects_fill_dead_entry.entry.get() - == self.current_object_data.get_data("fill_dead") - ) - and ( - self.objects_text_entry.entry.get() - == self.current_object_data.get_data("text") - ) - and ( - self.objects_health_entry.entry.get() - == str(self.current_object_data.get_data("health")) - ) - and ( - self.objects_density_entry.entry.get() - == str(self.current_object_data.get_data("density")) - ) - and (comp_ids == self.current_object_data.get_data("comp_ids")) - and ( - bool(self.objects_points_count_combo.combobox.current()) - == self.current_object_data.get_data("points_count") - ) - ) - ): - self.answer = askyesno( - title="confirmation", - message="""Warning: You have modified Object values and have not Updated. - Your changes will not be saved. Are you sure you would like continue?""", - ) - - if self.answer: - current_object = self.select_objects_combo.get() - self.current_object_data = self.object_data[current_object.split(":")[0]] - - self.show_object_entry(self.current_object_data) - else: - self.select_objects_combo.current(self.prev_object_combo) - - def change_maps_entry_widgets(self, event=None): - """ - Gets the correct map data for the currently selected team. - """ - self.answer = True - - if not ( - ( - ( - self.maps_name_entry.entry.get() - == self.current_map_data.get_data("name") - ) - and ( - self.maps_edge_obj_id_entry.entry.get() - == self.current_map_data.get_data("edge_obj_id") - ) - and ( - self.maps_desc_entry.entry.get() - == self.current_map_data.get_data("desc") - ) - and ( - int(self.maps_width_entry.entry.get()) - == self.current_map_data.get_data("width") - ) - and ( - int(self.maps_height_entry.entry.get()) - == self.current_map_data.get_data("height") - ) - ) - ): - self.answer = askyesno( - title="confirmation", - message="""Warning: You have modified Map values and have not Updated. - Your changes will not be saved. Are you sure you would like continue?""", - ) - if self.answer is True: - current_map_id = self.select_maps_combo.get() - self.current_map_data = self.map_data[current_map_id] - - self.show_map_entry(self.current_map_data) - else: - self.select_maps_combo.current() - - def show_component_entries(self, current_comp): - """ - Updates the values in the component entry widgets. - """ - self.component_type_attr = current_comp.view_keys - self.components_id_entry.entry.delete(0, tk.END) - self.components_id_entry.entry.insert(0, current_comp.get_data("id")) - self.components_name_entry.entry.delete(0, tk.END) - self.components_name_entry.entry.insert( - 0, - current_comp.get_data("name"), - ) - self.components_type_combo.configure(values=self.component_types) - self.components_type_combo.set(current_comp.get_data("ctype")) - self.components_type_label.config(text=current_comp.get_data("ctype")) - self.components_type_attr1_entry.entry.configure(state="normal") - self.components_type_attr2_entry.entry.configure(state="normal") - self.components_type_attr3_entry.entry.configure(state="normal") - self.components_type_attr4_entry.entry.configure(state="normal") - self.components_type_attr5_entry.entry.configure(state="normal") - self.components_type_attr6_entry.entry.configure(state="normal") - self.components_type_attr7_entry.entry.configure(state="normal") - self.components_type_attr1_entry.help_button.configure(state="normal") - self.components_type_attr2_entry.help_button.configure(state="normal") - self.components_type_attr3_entry.help_button.configure(state="normal") - self.components_type_attr4_entry.help_button.configure(state="normal") - self.components_type_attr5_entry.help_button.configure(state="normal") - self.components_type_attr6_entry.help_button.configure(state="normal") - self.components_type_attr7_entry.help_button.configure(state="normal") - self.components_type_attr1_label.config(text="") - self.components_type_attr2_label.config(text="") - self.components_type_attr3_label.config(text="") - self.components_type_attr4_label.config(text="") - self.components_type_attr5_label.config(text="") - self.components_type_attr6_label.config(text="") - self.components_type_attr7_label.config(text="") - self.components_type_attr1_entry.entry.delete(0, tk.END) - self.components_type_attr1_entry.entry.config( - validate="key", validatecommand=(self.validate_num, "%P") - ) - self.components_type_attr2_entry.entry.delete(0, tk.END) - self.components_type_attr3_entry.entry.delete(0, tk.END) - self.components_type_attr3_entry.entry.config( - validate="key", validatecommand=(self.validate_num, "%P") - ) - self.components_type_attr4_entry.entry.delete(0, tk.END) - self.components_type_attr5_entry.entry.delete(0, tk.END) - self.components_type_attr6_entry.entry.delete(0, tk.END) - self.components_type_attr7_entry.entry.delete(0, tk.END) - - if current_comp.get_data("ctype") == "CnC": - self.components_type_attr1_label.config(text=self.component_type_attr[4]) - self.components_type_attr1_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[4]) - ) - self.components_type_attr2_entry.entry.configure(state="readonly") - self.components_type_attr3_entry.entry.configure(state="readonly") - self.components_type_attr4_entry.entry.configure(state="readonly") - self.components_type_attr5_entry.entry.configure(state="readonly") - self.components_type_attr6_entry.entry.configure(state="readonly") - self.components_type_attr7_entry.entry.configure(state="readonly") - self.components_type_attr2_entry.help_button.configure(state="disabled") - self.components_type_attr3_entry.help_button.configure(state="disabled") - self.components_type_attr4_entry.help_button.configure(state="disabled") - self.components_type_attr5_entry.help_button.configure(state="disabled") - self.components_type_attr6_entry.help_button.configure(state="disabled") - self.components_type_attr7_entry.help_button.configure(state="disabled") - elif current_comp.get_data("ctype") == "FixedGun": - self.components_type_attr3_entry.entry.configure(validate="none") - self.components_type_attr1_label.config(text=self.component_type_attr[4]) - self.components_type_attr1_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[4]) - ) - self.components_type_attr2_label.config(text=self.component_type_attr[5]) - self.components_type_attr2_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[5]) - ) - self.components_type_attr3_label.config(text=self.component_type_attr[6]) - self.components_type_attr3_entry.entry.insert( - 0, - ( - current_comp.get_data(self.component_type_attr[6]) - if current_comp.get_data("reloading") is not False - else "False" - ), - ) - self.components_type_attr3_entry.entry.configure( - state="readonly", validate="none" - ) - self.components_type_attr4_label.config(text=self.component_type_attr[7]) - self.components_type_attr4_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[7]) - ) - self.components_type_attr5_label.config(text=self.component_type_attr[8]) - self.components_type_attr5_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[8]) - ) - self.components_type_attr6_label.config(text=self.component_type_attr[9]) - self.components_type_attr6_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[9]) - ) - self.components_type_attr7_label.config(text=self.component_type_attr[10]) - self.components_type_attr7_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[10]) - ) - elif current_comp.get_data("ctype") == "Engine": - self.components_type_attr1_label.config(text=self.component_type_attr[4]) - self.components_type_attr1_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[4]) - ) - self.components_type_attr2_label.config(text=self.component_type_attr[5]) - self.components_type_attr2_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[5]) - ) - self.components_type_attr3_label.config(text=self.component_type_attr[6]) - self.components_type_attr3_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[6]) - ) - self.components_type_attr4_label.config(text=self.component_type_attr[7]) - self.components_type_attr4_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[7]) - ) - self.components_type_attr5_label.config(text=self.component_type_attr[8]) - self.components_type_attr5_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[8]) - ) - self.components_type_attr6_entry.entry.configure(state="readonly") - self.components_type_attr7_entry.entry.configure(state="readonly") - self.components_type_attr6_entry.help_button.configure(state="disabled") - self.components_type_attr7_entry.help_button.configure(state="disabled") - elif current_comp.get_data("ctype") == "Radar": - self.components_type_attr1_entry.entry.configure(validate="none") - self.components_type_attr1_label.config(text=self.component_type_attr[4]) - self.components_type_attr1_entry.entry.insert( - 0, - ( - current_comp.get_data(self.component_type_attr[4]) - if current_comp.get_data("active") is not False - else "False" - ), - ) - self.components_type_attr1_entry.entry.configure( - state="readonly", validate="none" - ) - self.components_type_attr2_label.config(text=self.component_type_attr[5]) - self.components_type_attr2_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[5]) - ) - self.components_type_attr3_label.config(text=self.component_type_attr[6]) - self.components_type_attr3_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[6]) - ) - self.components_type_attr4_label.config(text=self.component_type_attr[7]) - self.components_type_attr4_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[7]) - ) - self.components_type_attr5_label.config(text=self.component_type_attr[8]) - self.components_type_attr5_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[8]) - ) - self.components_type_attr6_label.config(text=self.component_type_attr[9]) - self.components_type_attr6_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[9]) - ) - self.components_type_attr7_entry.entry.configure(state="readonly") - self.components_type_attr7_entry.help_button.configure(state="disabled") - elif current_comp.get_data("ctype") == "Radio": - self.components_type_attr3_entry.entry.configure(validate="none") - self.components_type_attr1_label.config(text="max_range") - self.components_type_attr1_entry.entry.insert( - 0, current_comp.get_data("max_range") - ) - self.components_type_attr2_label.config(text="cur_range") - self.components_type_attr2_entry.entry.insert( - 0, current_comp.get_data("cur_range") - ) - self.components_type_attr3_label.config(text="message") - self.components_type_attr3_entry.entry.insert( - 0, - ( - current_comp.get_data("message") - if current_comp.get_data("message") is not None - else "null" - ), - ) - self.components_type_attr3_entry.entry.configure(state="readonly") - self.components_type_attr4_entry.entry.configure(state="readonly") - self.components_type_attr5_entry.entry.configure(state="readonly") - self.components_type_attr6_entry.entry.configure(state="readonly") - self.components_type_attr7_entry.entry.configure(state="readonly") - self.components_type_attr4_entry.help_button.configure(state="disabled") - self.components_type_attr5_entry.help_button.configure(state="disabled") - self.components_type_attr6_entry.help_button.configure(state="disabled") - self.components_type_attr7_entry.help_button.configure(state="disabled") - elif self.current_component_data.get_data("ctype") == "Arm": - self.components_type_attr3_entry.entry.configure(validate="none") - self.components_type_attr1_label.config(text=self.component_type_attr[4]) - self.components_type_attr1_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[4]) - ) - self.components_type_attr2_label.config(text=self.component_type_attr[5]) - self.components_type_attr2_entry.entry.insert( - 0, current_comp.get_data(self.component_type_attr[5]) - ) - self.components_type_attr3_label.config(text=self.component_type_attr[6]) - self.components_type_attr3_entry.entry.insert( - 0, - ( - current_comp.get_data(self.component_type_attr[6]) - if current_comp.get_data(self.component_type_attr[6]) is not None - else "null" - ), - ) - self.components_type_attr3_entry.entry.configure(state="readonly") - self.components_type_attr4_entry.entry.configure(state="readonly") - self.components_type_attr5_entry.entry.configure(state="readonly") - self.components_type_attr6_entry.entry.configure(state="readonly") - self.components_type_attr7_entry.entry.configure(state="readonly") - self.components_type_attr4_entry.help_button.configure(state="disabled") - self.components_type_attr5_entry.help_button.configure(state="disabled") - self.components_type_attr6_entry.help_button.configure(state="disabled") - self.components_type_attr7_entry.help_button.configure(state="disabled") - self.components_column.update_idletasks() - - def show_team_entry(self, current_team): - """ - Updates the values stored in the team entry widgets. - """ - self.team_name_entry.entry.delete(0, tk.END) - self.team_name_entry.entry.insert(0, current_team["name"]) - self.team_size_entry.entry.delete(0, tk.END) - self.team_size_entry.entry.insert(0, current_team["size"]) - self.callsign_entry.entry.delete(0, tk.END) - self.callsign_entry.entry.insert( - 0, self.current_team_data["agent_defs"][0]["callsign"] - ) - self.squad_entry.entry.delete(0, tk.END) - self.squad_entry.entry.insert(0, current_team["agent_defs"][0]["squad"]) - self.agent_object_entry.entry.delete(0, tk.END) - self.agent_object_entry.entry.insert(0, current_team["agent_defs"][0]["object"]) - self.ai_file_entry.entry.delete(0, tk.END) - self.ai_file_entry.entry.insert(0, current_team["agent_defs"][0]["AI_file"]) - - def show_map_entry(self, current_map): - """ - Updates the values stored in the map entry widgets. - """ - self.maps_id_entry.entry.delete(0, tk.END) - self.maps_id_entry.entry.insert(0, self.select_maps_combo.get()) - self.maps_name_entry.entry.delete(0, tk.END) - self.maps_name_entry.entry.insert(0, current_map.get_data("name")) - self.maps_edge_obj_id_entry.entry.delete(0, tk.END) - self.maps_edge_obj_id_entry.entry.insert( - 0, self.current_map_data.get_data("edge_obj_id") - ) - self.maps_desc_entry.entry.delete(0, tk.END) - self.maps_desc_entry.entry.insert(0, current_map.get_data("desc")) - self.maps_width_entry.entry.delete(0, tk.END) - self.maps_width_entry.entry.insert(0, current_map.get_data("width")) - self.maps_height_entry.entry.delete(0, tk.END) - self.maps_height_entry.entry.insert(0, current_map.get_data("height")) - - def show_object_entry(self, current_obj): - """ - Updates the values stored in the object entry widgets. - """ - self.objects_id_entry.entry.delete(0, tk.END) - self.objects_id_entry.entry.insert(0, current_obj.get_data("id")) - self.objects_name_entry.entry.delete(0, tk.END) - self.objects_name_entry.entry.insert(0, current_obj.get_data("name")) - self.objects_fill_alive_entry.entry.delete(0, tk.END) - self.objects_fill_alive_entry.entry.insert( - 0, current_obj.get_data("fill_alive") - ) - self.objects_fill_dead_entry.entry.delete(0, tk.END) - self.objects_fill_dead_entry.entry.insert(0, current_obj.get_data("fill_dead")) - self.objects_text_entry.entry.delete(0, tk.END) - self.objects_text_entry.entry.insert(0, current_obj.get_data("text")) - self.objects_health_entry.entry.delete(0, tk.END) - self.objects_health_entry.entry.insert(0, current_obj.get_data("health")) - self.objects_density_entry.entry.delete(0, tk.END) - self.objects_density_entry.entry.insert(0, current_obj.get_data("density")) - self.current_comp_ids = current_obj.get_data("comp_ids")[:] - self.objects_comp_ids_combo.combobox.set("") - self.objects_comp_ids_combo.combobox.configure(values=self.current_comp_ids) - if len(self.current_comp_ids) != 0: - self.objects_comp_ids_combo.combobox.current(0) - self.objects_comp_ids_combo.combobox.bind( - "<>", self.get_current_comp_id - ) - self.objects_comp_ids_combo.combobox.bind("", self.add_empty_comp_id) - self.objects_comp_ids_combo.combobox.bind("", self.add_new_comp_id) - self.objects_comp_ids_combo.combobox.bind("", self.delete_comp_id) - self.objects_points_count_combo.combobox.configure(values=["False", "True"]) - self.objects_points_count_combo.combobox.config(state="normal") - if bool(current_obj.get_data("points_count")) is True: - self.objects_points_count_combo.combobox.current(1) - else: - self.objects_points_count_combo.combobox.current(0) - self.objects_points_count_combo.combobox.config(state="readonly") - - def add_empty_comp_id(self, event): - """ - Adds an empty component ID for users to select to add new component ID. - """ - if len(self.current_comp_ids) == 0: - self.current_comp_ids.append("Add New Comp ID") - self.objects_comp_ids_combo.combobox.configure(values=self.current_comp_ids) - elif self.current_comp_ids[-1] != "Add New Comp ID": - self.current_comp_ids.append("Add New Comp ID") - self.objects_comp_ids_combo.combobox.configure(values=self.current_comp_ids) - - def get_current_comp_id(self, event): - """ - Gets the current comp id selected for the current object. - """ - self.current_comp_id_idx = self.objects_comp_ids_combo.combobox.current() - self.current_comp_id = self.objects_comp_ids_combo.combobox.get() - - def delete_comp_id(self, event): - """ - Removes the current component ID from the list. - """ - current_comp_id = self.objects_comp_ids_combo.combobox.get() - if len(current_comp_id) == 0: - if self.current_comp_id_idx != len(self.current_comp_ids) - 1: - self.current_comp_ids.pop(self.current_comp_id_idx) - self.objects_comp_ids_combo.combobox.configure( - values=self.current_comp_ids - ) - - def add_new_comp_id(self, event): - """ - Adds the a new component ID to the list. - """ - new_combo_id = self.objects_comp_ids_combo.combobox.get() - new_combo_id_index = self.current_comp_id_idx - if new_combo_id not in self.current_comp_ids and new_combo_id.strip() != "": - self.current_comp_ids[new_combo_id_index] = new_combo_id - self.objects_comp_ids_combo.combobox.configure(values=self.current_comp_ids) - self.objects_comp_ids_combo.combobox.set(new_combo_id) - - ### UPDATE JSON FILES### - def update_teams_json(self): - """ - Updates the teams JSON values. - """ - if ( - self.team_name_entry.entry.get() in self.team_data.keys() - and self.team_name_entry.entry.get() != self.select_team_combo.get() - ): - showwarning( - title="Warning", - message="The name you are trying to use is already in use by another team. Please use another name.", - ) - else: - if ( - self.team_size_entry.entry.get() != "" - and self.callsign_entry.entry.get() != "" - and self.team_name_entry.entry.get() != "" - and self.squad_entry.entry.get() != "" - and self.agent_object_entry.entry.get() != "" - and self.ai_file_entry.entry.get() != "" - ): - self.current_team_data["size"] = int(self.team_size_entry.entry.get()) - self.current_team_data["agent_defs"][0][ - "callsign" - ] = self.callsign_entry.entry.get() - self.current_team_data["name"] = self.team_name_entry.entry.get() - self.current_team_data["agent_defs"][0][ - "squad" - ] = self.squad_entry.entry.get() - self.current_team_data["agent_defs"][0][ - "object" - ] = self.agent_object_entry.entry.get() - self.current_team_data["agent_defs"][0][ - "AI_file" - ] = self.ai_file_entry.entry.get() - - self.team_data.update( - {self.current_team_data["name"]: self.current_team_data} - ) - - self.teams_json = json.dumps(self.team_data, indent=4) - - with open("settings/teams.json", "r") as f: - team_json = json.load(f) - - if self.current_team_data["name"] != self.select_team_combo.get(): - if self.select_team_combo.get() in team_json: - team_json.pop(self.select_team_combo.get()) - if self.select_team_combo.get() in self.team_data: - self.team_data.pop(self.select_team_combo.get()) - self.team_names.pop(self.select_team_combo.current()) - self.team_names.append(self.current_team_data["name"]) - self.select_team_combo.config(values=self.team_names) - self.select_team_combo.current(len(self.team_names) - 1) - - team_json[self.current_team_data["name"]] = self.current_team_data - - self.team_data[self.current_team_data["name"]] = self.current_team_data - - f.close() - - with open("settings/teams.json", "w") as f: - json.dump(team_json, f, indent=4) - f.close() - - def update_components_json(self): - """ - Updates the components JSON. values - """ - if ( - self.components_id_entry.entry.get() in self.component_data.keys() - and self.components_id_entry.entry.get() - != self.select_component_combo.get().split(":")[0] - ): - showwarning( - title="Warning", - message="The ID you are trying to use is already in use by another component. Please use another ID.", - ) - else: - if ( - self.components_id_entry.entry.get() != "" - and self.components_name_entry.entry.get() != "" - and self.components_type_attr1_entry.entry.get() != "" - ): - self.current_component_data.setData( - "id", self.components_id_entry.entry.get() - ) - self.current_component_data.setData( - "name", self.components_name_entry.entry.get() - ) - self.current_component_data.setData( - "ctype", self.components_type_combo.get() - ) - if self.current_component_data.get_data("ctype") == "CnC": - self.current_component_data.setData( - "max_cmds_per_tick", - int(self.components_type_attr1_entry.entry.get()), - ) - if self.current_component_data.get_data("ctype") == "FixedGun": - self.current_component_data.setData( - "reload_ticks", - int(self.components_type_attr1_entry.entry.get()), - ) - self.current_component_data.setData( - "reload_ticks_remaining", - int(self.components_type_attr2_entry.entry.get()), - ) - self.current_component_data.setData( - "reloading", - ( - self.components_type_attr3_entry.entry.get() - if self.components_type_attr3_entry.entry.get() != "False" - else False - ), - ) - self.current_component_data.setData( - "ammunition", int(self.components_type_attr4_entry.entry.get()) - ) - self.current_component_data.setData( - "min_damage", int(self.components_type_attr5_entry.entry.get()) - ) - self.current_component_data.setData( - "max_damage", int(self.components_type_attr6_entry.entry.get()) - ) - self.current_component_data.setData( - "range", int(self.components_type_attr7_entry.entry.get()) - ) - if self.current_component_data.get_data("ctype") == "Engine": - self.current_component_data.setData( - "min_speed", float(self.components_type_attr1_entry.entry.get()) - ) - self.current_component_data.setData( - "max_speed", float(self.components_type_attr2_entry.entry.get()) - ) - self.current_component_data.setData( - "cur_speed", float(self.components_type_attr3_entry.entry.get()) - ) - self.current_component_data.setData( - "max_turnrate", - float(self.components_type_attr4_entry.entry.get()), - ) - self.current_component_data.setData( - "cur_turnrate", - float(self.components_type_attr5_entry.entry.get()), - ) - if self.current_component_data.get_data("ctype") == "Radar": - self.current_component_data.setData( - "active", - ( - self.components_type_attr1_entry.entry.get() - if self.components_type_attr1_entry.entry.get() != "False" - else False - ), - ) - self.current_component_data.setData( - "range", int(self.components_type_attr2_entry.entry.get()) - ) - self.current_component_data.setData( - "level", int(self.components_type_attr3_entry.entry.get()) - ) - self.current_component_data.setData( - "visarc", int(self.components_type_attr4_entry.entry.get()) - ) - self.current_component_data.setData( - "offset_angle", - int(self.components_type_attr5_entry.entry.get()), - ) - self.current_component_data.setData( - "resolution", int(self.components_type_attr6_entry.entry.get()) - ) - if self.current_component_data.get_data("ctype") == "Radio": - self.current_component_data.setData( - "max_range", int(self.components_type_attr1_entry.entry.get()) - ) - self.current_component_data.setData( - "cur_range", int(self.components_type_attr2_entry.entry.get()) - ) - self.current_component_data.setData( - "message", - ( - self.components_type_attr3_entry.entry.get() - if self.components_type_attr3_entry.entry.get() != "" - else None - ), - ) - if self.current_component_data.get_data("ctype") == "Arm": - self.current_component_data.setData( - "max_weight", int(self.components_type_attr1_entry.entry.get()) - ) - self.current_component_data.setData( - "max_bulk", int(self.components_type_attr2_entry.entry.get()) - ) - self.current_component_data.setData( - "item", - ( - self.components_type_attr3_entry.entry.get() - if self.components_type_attr3_entry.entry.get() != "null" - else None - ), - ) - - with open("settings/components.json", "r") as f: - component_json = json.load(f) - - if ( - self.current_component_data.get_data("id") - != self.select_component_combo.get().split(":")[0] - ): - if ( - self.select_component_combo.get().split(":")[0] - in component_json - ): - component_json.pop( - self.select_component_combo.get().split(":")[0] - ) - if ( - self.select_component_combo.get().split(":")[0] - in self.component_data - ): - self.component_data.pop( - self.select_component_combo.get().split(":")[0] - ) - self.component_ids.pop(self.select_component_combo.current()) - self.component_ids.append( - self.current_component_data.get_data("id") - + ": " - + self.current_component_data.get_data("name") - ) - self.select_component_combo.configure(values=self.component_ids) - self.select_component_combo.current(len(self.component_ids) - 1) - - component_json[self.current_component_data.get_data("id")] = ( - self.current_component_data.getSelfView() - ) - f.close() - - self.component_data[self.current_component_data.get_data("id")] = ( - self.current_component_data - ) - - if self.select_component_combo.get().split(":")[1] != "" or ( - self.current_component_data.get_data("name") - != component_json[self.current_component_data.get_data("id")][ - "name" - ] - ): - self.component_ids.pop(self.select_component_combo.current()) - self.component_ids.append( - self.current_component_data.get_data("id") - + ": " - + self.current_component_data.get_data("name") - ) - self.select_component_combo.configure(values=self.component_ids) - self.select_component_combo.current(len(self.component_ids) - 1) - if self.current_component_data.get_data("name") != self.component_data[ - self.select_component_combo.get().split(":")[0] - ].get_data("name"): - comp_idx = self.select_component_combo.current() - self.current_comp_ids[comp_idx] = ": ".join( - [ - self.current_comp_ids[comp_idx].split(":")[0], - self.current_component_data.get_data("name"), - ] - ) - self.select_component_combo.configure(values=self.component_ids) - self.select_component_combo.current(len(self.component_ids) - 1) - if ( - "slot_id" - in component_json[self.current_component_data.get_data("id")].keys() - ): - component_json[self.current_component_data.get_data("id")].pop( - "slot_id" - ) - with open("settings/components.json", "w") as f: - json.dump(component_json, f, indent=4) - f.close() - - def update_objects_json(self): - """ - Updates the objects JSON values. - """ - if ( - self.objects_id_entry.entry.get() in self.object_data.keys() - and self.objects_id_entry.entry.get() - != self.select_objects_combo.get().split(":")[0] - ): - showwarning( - title="Warning", - message="The ID you are trying to use is already in use by another object. Please use another ID.", - ) - else: - if ( - self.objects_id_entry.entry.get() != "" - and self.objects_name_entry.entry.get() != "" - and self.objects_fill_dead_entry.entry.get() != "" - and self.objects_fill_alive_entry.entry.get() != "" - and self.objects_text_entry.entry.get() != "" - and self.objects_health_entry.entry.get() != "" - and self.objects_density_entry.entry.get() != "" - ): - self.current_object_data.setData( - "id", self.objects_id_entry.entry.get() - ) - self.current_object_data.setData( - "name", self.objects_name_entry.entry.get() - ) - self.current_object_data.setData( - "fill_alive", self.objects_fill_alive_entry.entry.get() - ) - self.current_object_data.setData( - "fill_dead", self.objects_fill_dead_entry.entry.get() - ) - self.current_object_data.setData( - "text", self.objects_text_entry.entry.get() - ) - self.current_object_data.setData( - "health", int(self.objects_health_entry.entry.get()) - ) - self.current_object_data.setData( - "density", int(self.objects_density_entry.entry.get()) - ) - if len(self.current_comp_ids) != 0: - if self.current_comp_ids[-1] == "Add New Comp ID": - self.current_comp_ids.pop(-1) - self.current_object_data.setData("comp_ids", self.current_comp_ids) - self.current_object_data.setData( - "points_count", - bool(self.objects_points_count_combo.combobox.current()), - ) - - with open("settings/objects.json", "r") as f: - object_json = json.load(f) - - if ( - self.current_object_data.get_data("id") - != self.select_objects_combo.get().split(":")[0] - ): - if self.select_objects_combo.get().split(":")[0] in object_json: - object_json.pop(self.select_objects_combo.get().split(":")[0]) - if self.select_objects_combo.get() in self.object_data: - self.object_data.pop(self.select_objects_combo.get()) - self.object_ids.pop(self.select_objects_combo.current()) - self.object_ids.append( - self.current_object_data.get_data("id") - + ": " - + self.current_object_data.get_data("name") - ) - self.select_objects_combo.configure(values=self.object_ids) - self.select_objects_combo.current(len(self.object_ids) - 1) - - object_json[self.current_object_data.get_data("id")] = ( - self.current_object_data.getJSONView() - ) - f.close() - - self.object_data[self.current_object_data.get_data("id")] = ( - self.current_object_data - ) - - if self.select_objects_combo.get().split(":")[1] != "" or ( - self.current_object_data.get_data("name") - != object_json[self.current_object_data.get_data("id")]["name"] - ): - self.object_ids.pop(self.select_objects_combo.current()) - self.object_ids.append( - self.current_object_data.get_data("id") - + ": " - + self.current_object_data.get_data("name") - ) - self.select_objects_combo.configure(values=self.object_ids) - self.select_objects_combo.current(len(self.object_ids) - 1) - - if self.current_object_data.get_data("name") != self.object_data[ - self.select_objects_combo.get().split(":")[0] - ].get_data("name"): - obj_idx = self.select_objects_combo.current() - self.object_ids[obj_idx] = ": ".join( - [ - self.object_ids[obj_idx].split(":")[0], - self.current_object_data.get_data("name"), - ] - ) - self.select_objects_combo.configure(values=self.object_ids) - self.select_objects_combo.current(len(self.object_ids) - 1) - - with open("settings/objects.json", "w") as f: - json.dump(object_json, f, indent=4) - f.close() - - def update_maps_json(self): - """ - Updates the maps JSON values. - """ - if ( - self.maps_id_entry.entry.get() in self.map_data.keys() - and self.maps_id_entry.entry.get() != self.select_maps_combo.get() - ): - showwarning( - title="Warning", - message="The ID you are trying to use is already in use by another map. Please use another ID.", - ) - else: - if ( - self.maps_name_entry.entry.get() != "" - and self.maps_edge_obj_id_entry.entry.get() != "" - and self.maps_desc_entry.entry.get() != "" - and self.maps_width_entry.entry.get != "" - and self.maps_height_entry.entry.get() != "" - ): - self.current_map_data.setData("name", self.maps_name_entry.entry.get()) - self.current_map_data.setData( - "edge_obj_id", self.maps_edge_obj_id_entry.entry.get() - ) - self.current_map_data.setData("desc", self.maps_desc_entry.entry.get()) - self.current_map_data.setData( - "width", int(self.maps_width_entry.entry.get()) - ) - self.current_map_data.setData( - "height", int(self.maps_height_entry.entry.get()) - ) - with open("settings/maps.json", "r") as f: - map_json = json.load(f) - if self.maps_id_entry.entry.get() != self.select_maps_combo.get(): - if self.select_maps_combo.get() in map_json: - map_json.pop(self.select_maps_combo.get()) - if self.select_maps_combo.get() in self.map_data: - self.map_data.pop(self.select_maps_combo.get()) - self.map_ids.pop(self.select_maps_combo.current()) - self.map_ids.append(self.maps_id_entry.entry.get()) - self.select_maps_combo.configure(values=self.map_ids) - self.select_maps_combo.current(len(self.map_ids) - 1) - - self.map_data[self.maps_id_entry.entry.get()] = self.current_map_data - map_json[self.maps_id_entry.entry.get()] = self.current_map_data.data - f.close() - - with open("settings/maps.json", "w") as f: - json.dump(map_json, f, indent=4) - f.close() - - ### CREATE NEW ### - - def create_team(self): - """ - Creates a new team and adds it to the team dictionary. - """ - self.team_id = askstring("Team ID", "Please enter an ID for the new team.") - while len(self.team_id) == 0 or self.team_id in self.team_data.keys(): - if len(self.team_id) == 0: - messagebox.showwarning( - "Warning", "You must enter a team ID to continue" - ) - else: - messagebox.showwarning( - "Warning", "This ID already exists, please enter a new ID." - ) - self.team_id = askstring("Team ID", "Please enter an ID for the new team.") - if len(self.team_id) != 0: - self.team_names.append(self.team_id) - self.select_team_combo.configure(values=self.team_names) - self.select_team_combo.current(len(self.team_names) - 1) - self.current_team_data = { - "size": "", - "name": self.team_id, - "agent_defs": [ - {"callsign": "", "squad": "", "object": "", "AI_file": ""} - ], - } - self.team_data.update({self.team_id: self.current_team_data}) - self.show_team_entry(self.current_team_data) - - def create_component(self): - """ - Creates a new component and adds it to the component dictionary. - """ - self.component_id = askstring( - "Component ID", "Please enter an ID for the new component." - ) - while ( - len(self.component_id) == 0 - or self.component_id in self.component_data.keys() - ): - if len(self.component_id) == 0: - messagebox.showwarning( - "Warning", "Please enter an ID for the new component" - ) - else: - messagebox.showwarning( - "Warning", "This ID already exists, please enter a new ID." - ) - self.component_id = askstring( - "Component ID", "Please enter an ID for the new component." - ) - if len(self.component_id) != 0: - self.component_ids.append(self.component_id + ": ") - self.select_component_combo.configure(values=self.component_ids) - self.select_component_combo.current(len(self.component_ids) - 1) - self.new_dict = {"id": self.component_id, "name": "", "ctype": ""} - - if self.components_type_combo.get() == "CnC": - self.new_dict["ctype"] = "CnC" - for key in self.cnc_keys: - self.new_dict[key] = "" - elif self.components_type_combo.get() == "FixedGun": - self.new_dict["ctype"] = "FixedGun" - for key in self.fixed_gun_keys: - self.new_dict[key] = "" - self.new_dict["reloading"] = False - elif self.components_type_combo.get() == "Engine": - self.new_dict["ctype"] = "Engine" - for key in self.engine_keys: - self.new_dict[key] = "" - elif self.components_type_combo.get() == "Radar": - self.new_dict["ctype"] = "Radar" - for key in self.radar_keys: - self.new_dict[key] = "" - self.new_dict["active"] = False - elif self.components_type_combo.get() == "Radio": - self.new_dict["ctype"] = "Radio" - for key in self.radio_keys: - self.new_dict[key] = "" - elif self.components_type_combo.get() == "Arm": - self.new_dict["ctype"] = "Arm" - for key in self.arm_keys: - self.new_dict[key] = "" - - self.current_component_data = comp.Comp(self.new_dict) - - self.component_data[self.component_id] = self.current_component_data - self.show_component_entries(self.current_component_data) - - def create_object(self): - """ - Creates a new object and adds it to the object dictionary. - """ - self.object_id = askstring( - "Object ID", "Please enter an ID for the new object." - ) - while len(self.object_id) == 0 or self.object_id in self.object_data: - if len(self.object_id) == 0: - messagebox.showwarning( - "Warning", "Please enter an ID for the new object" - ) - else: - messagebox.showwarning( - "Warning", "This ID already exists, please enter a new ID." - ) - self.object_id = askstring( - "Object ID", "Please enter an ID for the new object." - ) - if len(self.object_id) != 0: - self.object_ids.append(self.object_id + ": ") - self.select_objects_combo.configure(values=self.object_ids) - self.select_objects_combo.current(len(self.object_ids) - 1) - self.current_object_data = obj.Object( - { - "id": self.object_id, - "name": "", - "fill_alive": "", - "fill_dead": "", - "text": "", - "health": "", - "density": "", - "comp_ids": [], - "points_count": "", - } - ) - self.object_data[self.object_id] = self.current_object_data - self.show_object_entry(self.current_object_data) - - def create_map(self): - """ - Creates a new map and adds it to the map dictionary. - """ - self.map_name = askstring("Map Name", "Please enter a name for a new map.") - while len(self.map_name) == 0 or self.map_name in self.map_data.keys(): - if len(self.map_name) == 0: - messagebox.showwarning("Warning", "Please enter an ID for the new map") - else: - messagebox.showwarning( - "Warning", "This Name already exists, please enter a new Name." - ) - self.map_name = askstring("Map Name", "Please enter a name for a new map.") - if len(self.map_name) != 0: - self.map_ids.append(self.map_name) - self.select_maps_combo.configure(values=self.map_ids) - self.select_maps_combo.current(len(self.map_ids) - 1) - self.current_map_data = zmap.Map( - { - "name": "", - "edge_obj_id": "", - "desc": "", - "width": "", - "height": "", - "placed_objects": {}, - "placed_items": {}, - "sides": {}, - "win_states": [], - } - ) - self.map_data[self.map_name] = self.current_map_data - self.show_map_entry(self.current_map_data) - - ### DELETE ### - - def delete_team(self): - """ - Deletes the currently selected team from the JSON and team dictionary. - """ - if self.select_team_combo.get() in self.team_data: - self.team_data.pop(self.select_team_combo.get()) - self.team_names.pop(self.select_team_combo.current()) - - with open("settings/teams.json", "r") as f: - team_json = json.load(f) - f.close() - team_json.pop(self.select_team_combo.get()) - with open("settings/teams.json", "w") as f: - json.dump(team_json, f, indent=4) - f.close() - - self.select_team_combo.configure(values=self.team_names) - self.select_team_combo.current(len(self.team_names) - 1) - self.change_team_entry_widgets() - - def delete_components(self): - """ - Deletes the currently selected component from the JSON and component dictionary. - """ - if self.select_component_combo.get().split(":")[0] in self.component_data: - self.component_data.pop(self.select_component_combo.get().split(":")[0]) - - with open("settings/components.json", "r") as f: - component_json = json.load(f) - component_json.pop(self.select_component_combo.get().split(":")[0]) - f.close() - with open("settings/components.json", "w") as f: - json.dump(component_json, f, indent=4) - f.close() - self.component_ids.pop(self.select_component_combo.current()) - self.select_component_combo.configure(values=self.component_ids) - self.select_component_combo.current(len(self.component_ids) - 1) - self.change_components_entry_widgets() - - def delete_object(self): - """ - Deletes the currently selected object from the JSON and object dictionary. - """ - if self.select_objects_combo.get().split(":")[0] in self.object_data: - self.object_data.pop(self.select_objects_combo.get().split(":")[0]) - - with open("settings/objects.json", "r") as f: - object_json = json.load(f) - object_json.pop(self.select_objects_combo.get().split(":")[0]) - f.close() - with open("settings/objects.json", "w") as f: - json.dump(object_json, f, indent=4) - f.close() - self.object_ids.pop(self.select_objects_combo.current()) - self.select_objects_combo.configure(values=self.object_ids) - self.select_objects_combo.current(len(self.object_ids) - 1) - self.change_objects_entry_widgets() - - def delete_map(self): - """ - Deletes the currently selected map from the JSON and map dictionary. - """ - if self.select_maps_combo.get() in self.map_data: - self.map_data.pop(self.select_maps_combo.get()) - with open("settings/maps.json", "r") as f: - map_json = json.load(f) - map_json.pop(self.select_maps_combo.get()) - f.close() - with open("settings/maps.json", "w") as f: - json.dump(map_json, f, indent=4) - f.close() - self.map_ids.pop(self.select_maps_combo.current()) - self.select_maps_combo.configure(values=self.map_ids) - self.select_maps_combo.current(len(self.map_ids) - 1) - self.change_maps_entry_widgets() - - ### SHOW MAP WINDOW ### - def show_map(self): - """ - Shows the UI representation of the currently selected map. - """ - self.ui_map = ui_map_config.UIMapConfig( - self.current_map_data, self, logger=self.logger - ) diff --git a/ui_component_config.py b/ui_component_config.py new file mode 100644 index 0000000..a55d2f8 --- /dev/null +++ b/ui_component_config.py @@ -0,0 +1,797 @@ +import tkinter as tk +import comp +import ui_widgets as uiw + + +class UINewComponent: + def __init__(self): + + self.comp_id_svar = tk.StringVar() + self.comp_ctype_svar = tk.StringVar() + self.comp_name_svar = tk.StringVar() + + self.top = tk.Toplevel() # use Toplevel() instead of Tk() + + self.label = uiw.uiLabel( + master=self.top, + text="Provide a unique ID, a name and the ctype of the new " + + "component.", + ) + self.label.grid(row=0, column=0, columnspan=2) + + self.id_label = uiw.uiLabel(master=self.top, text="ID") + self.id_label.grid(row=1, column=0) + self.id_entry = uiw.uiEntry(master=self.top) + self.id_entry.grid(row=1, column=1) + + self.ctype_label = uiw.uiLabel(master=self.top, text="CType") + self.ctype_label.grid(row=2, column=0) + # box_value = tk.StringVar() + self.combo = uiw.uiComboBox(master=self.top) + self.combo.config(values=comp.CTYPES_LIST) + self.combo.grid(row=2, column=1, padx=50, pady=10) + self.combo.bind("<>", lambda: ()) + + self.name_label = uiw.uiLabel(master=self.top, text="Name") + self.name_label.grid(row=3, column=0) + self.name_entry = uiw.uiEntry(master=self.top) + self.name_entry.grid(row=3, column=1, padx=50, pady=10) + + self.btn = uiw.uiButton( + master=self.top, text="Select", command=self.select + ) + self.btn.grid(row=4, column=0, columnspan=2) + self.top.wait_visibility() + self.top.grab_set() + self.top.wait_window( + self.top + ) # wait for itself destroyed, so like a modal dialog + + def destroy(self): + self.top.destroy() + + def select(self): + self.comp_ctype = self.combo.get() + self.comp_id = self.id_entry.get() + self.comp_name = self.name_entry.get() + self.destroy() + + def get_result(self): + return { + "id": self.comp_id, + "ctype": self.comp_ctype, + "name": self.comp_name + } + + +class UIComponentConfig(tk.Frame): + def __init__(self, controller, ldr, master=None, logger=None): + super().__init__(master) + self.controller = controller + self.master = master + self.configure(bg=uiw.BGCOLOR) + + self.logger = logger + self.ldr = ldr + + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(0, weight=1) + self.grid_columnconfigure(1, weight=1) + self.grid_columnconfigure(2, weight=1) + self.fixed_gun_keys = [ + "reload_ticks", + "reload_ticks_remaining", + "reloading", + "ammunition", + "min_damage", + "max_damage", + "range", + ] + self.engine_keys = [ + "min_speed", + "max_speed", + "cur_speed", + "max_turnrate", + "cur_turnrate", + ] + self.radar_keys = [ + "active", + "range", + "level", + "visarc", + "offset_angle", + "resolution", + ] + self.cnc_keys = ["max_cmds_per_tick"] + self.radio_keys = ["max_range", "cur_range", "message"] + self.arm_keys = ["max_weight", "max_bulk", "item"] + + self.prev_component_combo = None + + self.build_ui() + + self.ui_map = None + + def validate_number_entry(self, input): + """ + Validates each entered value (input) to ensure it is a number. + """ + input.replace(".", "", 1) + input.replace("-", "", 1) + if input.isdigit() or input == "" or "-" in input or "." in input: + return True + + else: + return False + + def get_focused_entry(self): + """ + Returns the currently focused entry in advanced config. + """ + focused_entry = self.focus_get() + return focused_entry + + def build_ui(self): + """ + Initializes all widgets and places them. + """ + # Make frames + self.main_frame = uiw.uiQuietFrame(master=self) + self.component_selection_frame = uiw.uiLabelFrame( + master=self.main_frame, text="Component List" + ) + self.button_row = uiw.uiQuietFrame(master=self.main_frame) + self.title_label = uiw.uiLabel( + master=self.main_frame, text="Component Config" + ) + self.validate_num = self.main_frame.register( + self.validate_number_entry + ) + + # Component Attribute Columns + self.comp_general_frame = uiw.uiLabelFrame( + master=self.main_frame, text="General Data" + ) + self.comp_fixed_gun_frame = uiw.uiLabelFrame( + master=self.main_frame, text="FixedGun Data" + ) + self.comp_engine_frame = uiw.uiLabelFrame( + master=self.main_frame, text="Engine Data" + ) + self.comp_radar_frame = uiw.uiLabelFrame( + master=self.main_frame, text="Radar Data" + ) + self.comp_cnc_frame = uiw.uiLabelFrame( + master=self.main_frame, text="CnC Data" + ) + self.comp_radio_frame = uiw.uiLabelFrame( + master=self.main_frame, text="Radio Data" + ) + self.comp_arm_frame = uiw.uiLabelFrame( + master=self.main_frame, text="Arm Data" + ) + self.current_comp_attribute_frame = None + + # Place frames + self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + self.title_label.pack(side=tk.TOP, fill="x") + self.button_row.pack(side=tk.BOTTOM, fill="x") + self.component_selection_frame.pack(side=tk.LEFT, fill="y") + self.comp_general_frame.pack(side=tk.LEFT, fill="y") + + # CType selection + self.select_component_type_combo = uiw.uiComboBox( + master=self.component_selection_frame + ) + self.select_component_type_combo.configure( + state="readonly", values=comp.CTYPES_LIST + ) + self.select_component_type_combo.pack(side=tk.TOP, fill=tk.BOTH) + self.select_component_type_combo.bind( + "<>", self.populate_comp_listbox + ) + + # Component Selection widgets + self.select_comp_listbox_var = tk.StringVar() + self.select_comp_listbox = uiw.uiListBox( + master=self.component_selection_frame, + listvariable=self.select_comp_listbox_var, + selectmode="browse", + ) + self.select_comp_listbox.pack(side=tk.LEFT, fill=tk.BOTH) + self.select_comp_listbox.bind("<>", self.show_component) + + self.build_general_comp_ui() + self.build_fixed_gun_comp_ui() + self.build_engine_comp_ui() + self.build_radar_comp_ui() + self.build_cnc_comp_ui() + self.build_radio_comp_ui() + self.build_arm_comp_ui() + + # Buttons + self.components_create_button = uiw.uiButton( + master=self.button_row, + command=self.create_component, + text="Add Component" + ) + self.components_create_button.pack(side=tk.LEFT) + + self.components_update_button = uiw.uiButton( + master=self.button_row, + command=self.update_component, + text="Update Component", + ) + self.components_update_button.pack(side=tk.LEFT) + + self.components_delete_button = uiw.uiCarefulButton( + master=self.button_row, + command=self.delete_components, + text="Delete Component", + ) + self.components_delete_button.pack(side=tk.LEFT) + + self.home_button = uiw.uiButton( + master=self.button_row, command=self.goto_home, text="Home" + ) + self.home_button.pack(side=tk.RIGHT) + self.save_to_json_button = uiw.uiCarefulButton( + master=self.button_row, + command=self.save_to_json, + text="Save Comps to JSON" + ) + self.save_to_json_button.pack(side=tk.RIGHT) + + self.populate_comp_listbox() + + def build_general_comp_ui(self): + self.components_id_label = uiw.uiLabel( + master=self.comp_general_frame, + text="ID:" + ) + self.components_id_entry = uiw.EntryHelp( + master=self.comp_general_frame, text="To be added." + ) + self.components_name_label = uiw.uiLabel( + master=self.comp_general_frame, text="Name:" + ) + self.components_name_entry = uiw.EntryHelp( + master=self.comp_general_frame, text="To be added." + ) + + self.components_id_label.grid(row=1, column=0, sticky="ew") + self.components_id_entry.frame.grid(row=1, column=1, sticky="ew") + self.components_name_label.grid(row=2, column=0, sticky="ew") + self.components_name_entry.frame.grid(row=2, column=1, sticky="ew") + + def build_fixed_gun_comp_ui(self): + + self.fg_reload_ticks_label = uiw.uiLabel( + master=self.comp_fixed_gun_frame, text="Reload Ticks" + ) + self.fg_reload_ticks_entry = uiw.EntryHelp( + master=self.comp_fixed_gun_frame, + text="The number of turns it takes the gun to reload.", + ) + self.fg_ammo_label = uiw.uiLabel( + master=self.comp_fixed_gun_frame, text="Ammunition" + ) + self.fg_ammo_entry = uiw.EntryHelp( + master=self.comp_fixed_gun_frame, text="The amount of starting " + + "ammunition." + ) + self.fg_min_damage_label = uiw.uiLabel( + master=self.comp_fixed_gun_frame, text="Min Damage" + ) + self.fg_min_damage_entry = uiw.EntryHelp( + master=self.comp_fixed_gun_frame, + text="The minimum damage the weapon causes.", + ) + self.fg_max_damage_label = uiw.uiLabel( + master=self.comp_fixed_gun_frame, text="Max Damage" + ) + self.fg_max_damage_entry = uiw.EntryHelp( + master=self.comp_fixed_gun_frame, + text="The maximum damage the weapon causes.", + ) + self.fg_wpn_range_label = uiw.uiLabel( + master=self.comp_fixed_gun_frame, text="Range" + ) + self.fg_wpn_range_entry = uiw.EntryHelp( + master=self.comp_fixed_gun_frame, + text="The maximum distance a projectile will travel.", + ) + + self.fg_reload_ticks_label.grid(row=1, column=0, sticky="ew") + self.fg_reload_ticks_entry.frame.grid(row=1, column=1, sticky="ew") + self.fg_reload_ticks_entry.entry.config( + validate="key", validatecommand=(self.validate_num, "%P") + ) + self.fg_ammo_label.grid(row=2, column=0, sticky="ew") + self.fg_ammo_entry.frame.grid(row=2, column=1, sticky="ew") + self.fg_min_damage_label.grid(row=3, column=0, sticky="ew") + self.fg_min_damage_entry.frame.grid(row=3, column=1, sticky="ew") + self.fg_max_damage_label.grid(row=4, column=0, sticky="ew") + self.fg_max_damage_entry.frame.grid(row=4, column=1, sticky="ew") + self.fg_wpn_range_label.grid(row=5, column=0, sticky="ew") + self.fg_wpn_range_entry.frame.grid(row=5, column=1, sticky="ew") + + def build_engine_comp_ui(self): + self.engine_min_speed_label = uiw.uiLabel( + master=self.comp_engine_frame, text="Min Speed" + ) + self.engine_min_speed_entry = uiw.EntryHelp( + master=self.comp_engine_frame, + text="The minimum speed of the engine. Negative values allow the " + + "object to move in reverse. If an object cannot reverse " + + "direction, min speed should be set to 0.0.", + ) + self.engine_max_speed_label = uiw.uiLabel( + master=self.comp_engine_frame, text="Max Speed" + ) + self.engine_max_speed_entry = uiw.EntryHelp( + master=self.comp_engine_frame, + text="The maximum speed of the engine. Speed directly translates " + + "into object velocity." + ) + self.engine_max_turnrate_label = uiw.uiLabel( + master=self.comp_engine_frame, text="Max Turnrate" + ) + self.engine_max_turnrate_entry = uiw.EntryHelp( + master=self.comp_engine_frame, + text="The maximum angle an object can rotate in one tick (in " + + "degrees). Turnrate is symmetric." + ) + self.engine_min_speed_label.grid(row=0, column=0, sticky="ew") + self.engine_min_speed_entry.frame.grid(row=0, column=1, sticky="ew") + self.engine_max_speed_label.grid(row=1, column=0, sticky="ew") + self.engine_max_speed_entry.frame.grid(row=1, column=1, sticky="ew") + self.engine_max_turnrate_label.grid(row=2, column=0, sticky="ew") + self.engine_max_turnrate_entry.frame.grid(row=2, column=1, sticky="ew") + + def build_radar_comp_ui(self): + self.radar_range_label = uiw.uiLabel( + master=self.comp_radar_frame, text="Radar Range" + ) + self.radar_range_entry = uiw.EntryHelp( + master=self.comp_radar_frame, + text="The maximum distance the radar can detect objects." + ) + self.radar_level_label = uiw.uiLabel( + master=self.comp_radar_frame, text="Radar Level" + ) + self.radar_level_entry = uiw.EntryHelp( + master=self.comp_radar_frame, + text="A higher radar allows the radar beam to pass through " + + "objects with a lower density. For example, a radar with a " + + "level of 100 can see through objects of density 0 to 99. " + + "However, it could not see through an object with a density " + + "of 100." + ) + self.radar_visarc_label = uiw.uiLabel( + master=self.comp_radar_frame, text="Visible Arc" + ) + self.radar_visarc_entry = uiw.EntryHelp( + master=self.comp_radar_frame, + text="A radar's visible arc (and resolution) determine how much " + + "of the environment the radar can see. A radar's with a visible " + + "arc extends from its offset angle in both directions. Within " + + "this arc around the offset, it sends out detection rays. " + + "How many rays and how far apart the rays are is determined by " + + "the 'resolution' attribute." + ) + self.radar_offset_angle_label = uiw.uiLabel( + master=self.comp_radar_frame, text="Offset Angle" + ) + self.radar_offset_angle_entry = uiw.EntryHelp( + master=self.comp_radar_frame, + text="The offset angle (in degrees) gives the direction the radar " + + "points relative to the front of the object. For example, an " + + "offset angle of 0 means the radar faces the same direction as " + + "the object. An offset of 180 would mean the radar sees behind " + + "the object." + ) + self.radar_resolution_label = uiw.uiLabel( + master=self.comp_radar_frame, text="Resolution" + ) + self.radar_resolution_entry = uiw.EntryHelp( + master=self.comp_radar_frame, + text="The resolution (in degrees) determines the interval " + + "between radar beams within its visible arc. For example, if a " + + "radar has an offset of 0, a visible arc of 20 and a resolution " + + "of 5, the radar will send out beams at the following degrees " + + "(as offsets from the object's facing): 20, 15, 10, 5, 0, -5, " + + "-10, -15, -20." + ) + + self.radar_range_label.grid(row=0, column=0, sticky="ew") + self.radar_range_entry.frame.grid(row=0, column=1, sticky="ew") + self.radar_level_label.grid(row=1, column=0, sticky="ew") + self.radar_level_entry.frame.grid(row=1, column=1, sticky="ew") + self.radar_visarc_label.grid(row=2, column=0, sticky="ew") + self.radar_visarc_entry.frame.grid(row=2, column=1, sticky="ew") + self.radar_offset_angle_label.grid(row=3, column=0, sticky="ew") + self.radar_offset_angle_entry.frame.grid(row=3, column=1, sticky="ew") + self.radar_resolution_label.grid(row=4, column=0, sticky="ew") + self.radar_resolution_entry.frame.grid(row=4, column=1, sticky="ew") + + def build_cnc_comp_ui(self): + self.cnc_max_commands_per_tick_label = uiw.uiLabel( + master=self.comp_cnc_frame, text="Max Cmds per Tick" + ) + self.cnc_max_commands_per_tick_entry = uiw.EntryHelp( + master=self.comp_cnc_frame, + text="The maximum number of commands, across all components, " + + "that can be processed in a given turn.", + ) + self.cnc_max_commands_per_tick_label.grid( + row=0, column=0, sticky="ew" + ) + self.cnc_max_commands_per_tick_entry.frame.grid( + row=0, column=1, sticky="ew" + ) + + def build_radio_comp_ui(self): + self.radio_max_broadcast_range_label = uiw.uiLabel( + master=self.comp_radio_frame, text="Max Broadcast Range" + ) + self.radio_max_broadcast_range_entry = uiw.EntryHelp( + master=self.comp_radio_frame, + text="The max distance from the sending object a message can " + + "be received." + ) + self.radio_max_broadcast_range_label.grid( + row=0, column=0, sticky="ew" + ) + self.radio_max_broadcast_range_entry.frame.grid( + row=0, column=1, sticky="ew" + ) + + def build_arm_comp_ui(self): + self.arm_max_weight_label = uiw.uiLabel( + master=self.comp_arm_frame, text="Max Weight" + ) + self.arm_max_weight_entry = uiw.EntryHelp( + master=self.comp_arm_frame, + text="The maximum weight the arm can lift." + ) + self.arm_max_bulk_label = uiw.uiLabel( + master=self.comp_arm_frame, text="Max Bulk" + ) + self.arm_max_bulk_entry = uiw.EntryHelp( + master=self.comp_arm_frame, + text="The maximum bulk the arm can manage." + ) + self.arm_max_weight_label.grid(row=0, column=0, sticky="ew") + self.arm_max_weight_entry.frame.grid(row=0, column=1, sticky="ew") + self.arm_max_bulk_label.grid(row=1, column=0, sticky="ew") + self.arm_max_bulk_entry.frame.grid(row=1, column=1, sticky="ew") + + def populate_comp_listbox(self, event=None): + + self.clear_all_fields() + + _ctype = self.select_component_type_combo.get() + self.show_ctype_attr_frame(_ctype) + + comp_data = self.ldr.get_comp_templates() + keys = [] + + for _comp in comp_data.values(): + if _comp["ctype"] == _ctype: + keys.append(f"{_comp['id']}:{_comp['name']}") + + self.select_comp_listbox.delete(0, tk.END) + self.select_comp_listbox_var.set(sorted(keys)) + + def show_ctype_attr_frame(self, _ctype): + + next_attr_frame = self.current_comp_attribute_frame + + match (_ctype): + case "FixedGun": + next_attr_frame = self.comp_fixed_gun_frame + case "Engine": + next_attr_frame = self.comp_engine_frame + case "Radar": + next_attr_frame = self.comp_radar_frame + case "CnC": + next_attr_frame = self.comp_cnc_frame + case "Radio": + next_attr_frame = self.comp_radio_frame + case "Arm": + next_attr_frame = self.comp_arm_frame + + if self.current_comp_attribute_frame != next_attr_frame: + if self.current_comp_attribute_frame is not None: + self.current_comp_attribute_frame.pack_forget() + self.current_comp_attribute_frame = next_attr_frame + self.current_comp_attribute_frame.pack(side=tk.LEFT, fill=tk.BOTH) + + def show_component(self, event=None): + selected_comp = self.get_currently_selected_component() + if selected_comp is not None: + self.clear_all_fields() + + self.components_id_entry.entry.insert(0, selected_comp["id"]) + self.components_name_entry.entry.insert(0, selected_comp["name"]) + + match (selected_comp["ctype"]): + case "FixedGun": + self.show_fixed_gun(selected_comp) + case "Engine": + self.show_engine(selected_comp) + case "Radar": + self.show_radar(selected_comp) + case "CnC": + self.show_cnc(selected_comp) + case "Radio": + self.show_radio(selected_comp) + case "Arm": + self.show_arm(selected_comp) + + def show_fixed_gun(self, comp): + self.fg_reload_ticks_entry.entry.insert(0, comp["reload_ticks"]) + self.fg_ammo_entry.entry.insert(0, comp["ammunition"]) + self.fg_min_damage_entry.entry.insert(0, comp["min_damage"]) + self.fg_max_damage_entry.entry.insert(0, comp["max_damage"]) + self.fg_wpn_range_entry.entry.insert(0, comp["range"]) + + def show_engine(self, comp): + self.engine_min_speed_entry.entry.insert(0, comp["min_speed"]) + self.engine_max_speed_entry.entry.insert(0, comp["max_speed"]) + self.engine_max_turnrate_entry.entry.insert(0, comp["max_turnrate"]) + + def show_radar(self, comp): + self.radar_range_entry.entry.insert(0, comp["range"]) + self.radar_level_entry.entry.insert(0, comp["level"]) + self.radar_visarc_entry.entry.insert(0, comp["visarc"]) + self.radar_offset_angle_entry.entry.insert(0, comp["offset_angle"]) + self.radar_resolution_entry.entry.insert(0, comp["resolution"]) + + def show_cnc(self, comp): + self.cnc_max_commands_per_tick_entry.entry.insert( + 0, comp["max_cmds_per_tick"] + ) + + def show_radio(self, comp): + self.radio_max_broadcast_range_entry.entry.insert(0, comp["max_range"]) + + def show_arm(self, comp): + self.arm_max_weight_entry.entry.insert(0, comp["max_weight"]) + self.arm_max_bulk_entry.entry.insert(0, comp["max_bulk"]) + + def clear_all_fields(self): + # General + self.components_id_entry.entry.delete(0, tk.END) + self.components_name_entry.entry.delete(0, tk.END) + # FixedGun + self.fg_reload_ticks_entry.entry.delete(0, tk.END) + self.fg_ammo_entry.entry.delete(0, tk.END) + self.fg_min_damage_entry.entry.delete(0, tk.END) + self.fg_max_damage_entry.entry.delete(0, tk.END) + self.fg_wpn_range_entry.entry.delete(0, tk.END) + # Engine + self.engine_min_speed_entry.entry.delete(0, tk.END) + self.engine_max_speed_entry.entry.delete(0, tk.END) + self.engine_max_turnrate_entry.entry.delete(0, tk.END) + # Radar + self.radar_range_entry.entry.delete(0, tk.END) + self.radar_level_entry.entry.delete(0, tk.END) + self.radar_visarc_entry.entry.delete(0, tk.END) + self.radar_offset_angle_entry.entry.delete(0, tk.END) + self.radar_resolution_entry.entry.delete(0, tk.END) + # CnC + self.cnc_max_commands_per_tick_entry.entry.delete(0, tk.END) + # Radio + self.radio_max_broadcast_range_entry.entry.delete(0, tk.END) + # Arm + self.arm_max_weight_entry.entry.delete(0, tk.END) + self.arm_max_bulk_entry.entry.delete(0, tk.END) + + def get_currently_selected_component(self): + comp_index = self.select_comp_listbox.curselection() + if len(comp_index) == 1: + comp_entry = self.select_comp_listbox.get(comp_index[0]) + comp_id, comp_name = comp_entry.split(":") + return self.ldr.get_comp_template(comp_id) + else: + return None + + def select_comp_with_id(self, _id): + for index, entry in enumerate(self.select_comp_listbox.get(0, tk.END)): + comp_id, comp_name = entry.split(":") + if _id == comp_id: + self.select_comp_listbox.selection_clear(0, tk.END) + self.select_comp_listbox.selection_set(index) + self.select_comp_listbox.activate(index) + self.show_component() + break + + def update_component(self): + """ + Updates the components JSON. values + """ + comp_data = self.ldr.get_comp_templates() + selected_comp = self.get_currently_selected_component() + if selected_comp is not None: + + new_id = self.components_id_entry.entry.get() + new_name = self.components_name_entry.entry.get() + + warnings = "" + + # Update ID: must check if unique + if new_id != selected_comp["id"]: + if new_id in self.ldr.get_comp_templates().keys(): + warnings += f"WARNING: The comp id {new_id} already " + + "exists. CompIDs must be unique.\n" + elif len(new_id) == 0: + warnings += "WARNING: ID cannot be empty.\n" + else: + old_id = selected_comp["id"] + selected_comp["id"] = new_id + del comp_data[old_id] + comp_data[new_id] = selected_comp + + if len(new_name) == 0: + warnings += "WARNING: Name cannot be empty.\n" + else: + selected_comp["name"] = new_name + + if len(warnings) > 0: + tk.messagebox.showwarning("Warning", warnings) + + # Update the ctype specific attributes + match selected_comp["ctype"]: + case "FixedGun": + self.update_fixed_gun(selected_comp) + case "Engine": + self.update_engine(selected_comp) + case "Radar": + self.update_radar(selected_comp) + case "CnC": + self.update_cnc(selected_comp) + case "Radio": + self.update_radio(selected_comp) + case "Arm": + self.update_arm(selected_comp) + + self.populate_comp_listbox(selected_comp["id"]) + self.select_comp_with_id(selected_comp["id"]) + + def update_fixed_gun(self, selected_comp): + try: + selected_comp["reload_ticks"] = int( + self.fg_reload_ticks_entry.entry.get() + ) + selected_comp["ammunition"] = int( + self.fg_ammo_entry.entry.get() + ) + selected_comp["min_damage"] = int( + self.fg_min_damage_entry.entry.get() + ) + selected_comp["max_damage"] = int( + self.fg_max_damage_entry.entry.get() + ) + selected_comp["range"] = int( + self.fg_wpn_range_entry.entry.get() + ) + except ValueError as e: + tk.messagebox.showwarning("Warning", str(e)) + + def update_engine(self, selected_comp): + try: + selected_comp["min_speed"] = float( + self.engine_min_speed_entry.entry.get() + ) + selected_comp["max_speed"] = float( + self.engine_max_speed_entry.entry.get() + ) + selected_comp["max_turnrate"] = float( + self.engine_max_turnrate_entry.entry.get() + ) + except ValueError as e: + tk.messagebox.showwarning("Warning", str(e)) + + def update_radar(self, selected_comp): + try: + selected_comp["range"] = int(self.radar_range_entry.entry.get()) + selected_comp["level"] = int(self.radar_level_entry.entry.get()) + selected_comp["visarc"] = int(self.radar_visarc_entry.entry.get()) + selected_comp["offset_angle"] = int( + self.radar_offset_angle_entry.entry.get() + ) + selected_comp["resolution"] = int( + self.radar_resolution_entry.entry.get() + ) + except ValueError as e: + tk.messagebox.showwarning("Warning", str(e)) + + def update_cnc(self, selected_comp): + try: + selected_comp["max_cmds_per_tick"] = int( + self.cnc_max_commands_per_tick_entry.entry.get() + ) + except ValueError as e: + tk.messagebox.showwarning("Warning", str(e)) + + def update_radio(self, selected_comp): + try: + selected_comp["max_range"] = int( + self.radio_max_broadcast_range_entry.entry.get() + ) + except ValueError as e: + tk.messagebox.showwarning("Warning", str(e)) + + def update_arm(self, selected_comp): + try: + selected_comp["max_bulk"] = int( + self.arm_max_bulk_entry.entry.get() + ) + selected_comp["max_weight"] = int( + self.arm_max_weight_entry.entry.get() + ) + except ValueError as e: + tk.messagebox.showwarning("Warning", str(e)) + + def create_component(self): + """ + Creates a new component and adds it to the component dictionary. + """ + + comp_data = self.ldr.get_comp_templates() + + good = False + while not good: + + dialog = UINewComponent() + result = dialog.get_result() + + if result["ctype"] in comp.CTYPES_LIST: + if len(result["id"]) > 0 \ + and result["id"] not in comp_data.keys(): + if len(result["name"]) > 0: + good = True + else: + tk.messagebox.showwarning( + "Warning", + "A comp must have a name. Does not have to be " + + "unique." + ) + else: + tk.messagebox.showwarning( + "Warning", "A comp must have a non-empty, unique ID." + ) + else: + tk.messagebox.showwarning( + "Warning", "A comp must have a valid ctype." + ) + + comp_attrs = comp.COMP_ATTRS_BY_CTYPE[result["ctype"]] + + for attr, val in comp_attrs: + result[attr] = val + + comp_data.update({result["id"]: result}) + + self.populate_comp_listbox() + + def delete_components(self): + """ + Deletes the currently selected component from the JSON + and component dictionary. + """ + + component_data = self.ldr.get_comp_templates() + + selected_comp = self.get_currently_selected_component() + if selected_comp is not None: + del component_data[selected_comp["id"]] + self.populate_comp_listbox() + + def goto_home(self): + self.controller.show_frame("home_page") + + def save_to_json(self): + self.ldr.save_comp_templates() diff --git a/ui_gstate_config.py b/ui_gstate_config.py new file mode 100644 index 0000000..1affa13 --- /dev/null +++ b/ui_gstate_config.py @@ -0,0 +1,914 @@ +import tkinter as tk +import gstate +import ui_widgets as uiw + + +class UINewGoal: + def __init__(self): + + self.gstate_id_svar = tk.StringVar() + self.gstate_type_svar = tk.StringVar() + + self.top = tk.Toplevel() # use Toplevel() instead of Tk() + + self.label = uiw.uiLabel( + master=self.top, + text="Provide a unique ID and type for the new gstate." + ) + self.label.grid(row=0, column=0, columnspan=2) + + self.id_label = uiw.uiLabel(master=self.top, text="ID") + self.id_label.grid(row=1, column=0) + self.id_entry = uiw.uiEntry(master=self.top) + self.id_entry.grid(row=1, column=1) + + self.type_label = uiw.uiLabel(master=self.top, text="Type") + self.type_label.grid(row=2, column=0) + # box_value = tk.StringVar() + self.combo = uiw.uiComboBox(master=self.top) + self.combo.config(values=gstate.GSTATE_TYPES) + self.combo.grid(row=2, column=1, padx=50, pady=10) + self.combo.bind("<>", lambda: ()) + + self.btn = uiw.uiButton( + master=self.top, text="Select", command=self.select + ) + self.btn.grid(row=4, column=0, columnspan=2) + self.top.wait_visibility() + self.top.grab_set() + self.top.wait_window( + self.top + ) # wait for itself destroyed, so like a modal dialog + + def destroy(self): + self.top.destroy() + + def select(self): + self.gstate_type = self.combo.get() + self.gstate_id = self.id_entry.get() + self.destroy() + + def get_result(self): + return {"id": self.gstate_id, "type": self.gstate_type, "msg": ""} + + +class UIGetObjects: + def __init__(self, ldr, selectmode="single"): + + self.objs = [] + + self.top = tk.Toplevel() # use Toplevel() instead of Tk() + + self.label = uiw.uiLabel( + master=self.top, + text="Select one or more objects to add to the goal." + ) + self.label.grid(row=0, column=0) + + obj_entries = [] + obj_data = ldr.get_obj_templates() + for obj in obj_data.values(): + obj_entries.append(f"{obj['id']}:{obj['name']}") + + self.listbox_var = tk.StringVar() + self.listbox = uiw.uiListBox( + master=self.top, + listvariable=self.listbox_var, + selectmode=selectmode + ) + self.listbox_var.set(obj_entries) + self.listbox.grid(row=1, column=0, padx=50, pady=10) + # self.combo.bind("<>", lambda: ()) + + self.btn = uiw.uiButton( + master=self.top, text="Select", command=self.select + ) + self.btn.grid(row=2, column=0) + self.top.wait_visibility() + self.top.grab_set() + self.top.wait_window( + self.top + ) # wait for itself destroyed, so like a modal dialog + + def destroy(self): + self.top.destroy() + + def select(self): + selections = self.listbox.curselection() + for s in selections: + obj = self.listbox.get(s) + obj_id = obj.split(":")[0] + self.objs.append(obj_id) + self.destroy() + + def get_result(self): + return self.objs + + +class UIGetItems: + def __init__(self, ldr, selectmode="single"): + + self.items = [] + + self.top = tk.Toplevel() # use Toplevel() instead of Tk() + + self.label = uiw.uiLabel( + master=self.top, + text="Select one or more items to add to the goal." + ) + self.label.grid(row=0, column=0) + + item_entries = [] + item_data = ldr.get_item_templates() + for itm in item_data.values(): + item_entries.append(f"{itm['id']}:{itm['name']}") + + self.listbox_var = tk.StringVar() + self.listbox = uiw.uiListBox( + master=self.top, + listvariable=self.listbox_var, + selectmode=selectmode + ) + self.listbox_var.set(item_entries) + self.listbox.grid(row=1, column=0, padx=50, pady=10) + # self.combo.bind("<>", lambda: ()) + + self.btn = uiw.uiButton( + master=self.top, text="Select", command=self.select + ) + self.btn.grid(row=2, column=0) + self.top.wait_visibility() + self.top.grab_set() + self.top.wait_window( + self.top + ) # wait for itself destroyed, so like a modal dialog + + def destroy(self): + self.top.destroy() + + def select(self): + selections = self.listbox.curselection() + for s in selections: + itm = self.listbox.get(s) + itm_id = itm.split(":")[0] + self.items.append(itm_id) + self.destroy() + + def get_result(self): + return self.items + + +class UIGStateConfig(tk.Frame): + def __init__(self, controller, ldr, master=None, logger=None): + super().__init__(master) + self.controller = controller + self.master = master + self.configure(bg=uiw.BGCOLOR) + + self.logger = logger + self.ldr = ldr + + self.build_ui() + + def validate_number_entry(self, input): + """ + Validates each entered value (input) to ensure it is a number. + """ + input.replace(".", "", 1) + input.replace("-", "", 1) + if input.isdigit() or input == "" or "-" in input or "." in input: + return True + + else: + return False + + def build_ui(self): + """ + Initializes all widgets and places them. + """ + # Make frames + self.main_frame = uiw.uiQuietFrame(master=self) + self.goal_selection_frame = uiw.uiLabelFrame( + master=self.main_frame, text="Goal List" + ) + self.button_row = uiw.uiQuietFrame(master=self.main_frame) + self.title_label = uiw.uiLabel( + master=self.main_frame, text="Goal Config" + ) + self.validate_num = self.main_frame.register( + self.validate_number_entry + ) + + # Component Attribute Columns + self.goal_general_frame = uiw.uiLabelFrame( + master=self.main_frame, text="General Data" + ) + self.goal_items_touch_frame = uiw.uiLabelFrame( + master=self.main_frame, text="ITEMS_TOUCH Data" + ) + self.goal_obj_items_touch_frame = uiw.uiLabelFrame( + master=self.main_frame, text="OBJ_ITEMS_TOUCH Data" + ) + self.goal_n_objs_destroyed_frame = uiw.uiLabelFrame( + master=self.main_frame, text="N_OBJS_DESTROYED Data" + ) + self.goal_n_objs_in_locs_frame = uiw.uiLabelFrame( + master=self.main_frame, text="N_OBJS_IN_LOCS Data" + ) + + self.goal_general_frame.columnconfigure(0, weight=1) + + self.current_goal_attribute_frame = None + + # Place frames + self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + self.title_label.pack(side=tk.TOP, fill="x") + self.button_row.pack(side=tk.BOTTOM, fill="x") + self.goal_selection_frame.pack(side=tk.LEFT, fill="y") + self.goal_general_frame.pack(side=tk.LEFT, fill="y") + + # Goal type selection + self.select_goal_type_combo = uiw.uiComboBox( + master=self.goal_selection_frame + ) + self.select_goal_type_combo.configure( + state="readonly", values=gstate.GSTATE_TYPES + ) + self.select_goal_type_combo.pack(side=tk.TOP, fill=tk.BOTH) + self.select_goal_type_combo.bind( + "<>", self.populate_goal_listbox + ) + + # Component Selection widgets + self.select_goal_listbox_var = tk.StringVar() + self.select_goal_listbox = uiw.uiListBox( + master=self.goal_selection_frame, + listvariable=self.select_goal_listbox_var, + selectmode="single", + ) + self.select_goal_listbox.pack(side=tk.LEFT, fill=tk.BOTH) + self.select_goal_listbox.bind("<>", self.show_goal) + + self.build_general_goal_ui() + self.build_items_touch_ui() + self.build_obj_items_touch_ui() + self.build_n_objs_destroyed_ui() + self.build_n_objs_in_locs_ui() + + # Buttons + self.new_goal_button = uiw.uiButton( + master=self.button_row, command=self.new_goal, text="Create Goal" + ) + self.new_goal_button.pack(side=tk.LEFT) + + self.update_goal_button = uiw.uiButton( + master=self.button_row, + command=self.update_goal, + text="Update Goal", + ) + self.update_goal_button.pack(side=tk.LEFT) + + self.delete_goal_button = uiw.uiCarefulButton( + master=self.button_row, + command=self.delete_goal, + text="Delete Goal" + ) + self.delete_goal_button.pack(side=tk.LEFT) + + self.home_button = uiw.uiButton( + master=self.button_row, command=self.goto_home, text="Home" + ) + self.home_button.pack(side=tk.RIGHT) + self.save_to_json_button = uiw.uiCarefulButton( + master=self.button_row, + command=self.save_to_json, + text="Save Comps to JSON" + ) + self.save_to_json_button.pack(side=tk.RIGHT) + + self.populate_goal_listbox() + + def build_general_goal_ui(self): + self.goal_id_label = uiw.uiLabel( + master=self.goal_general_frame, + text="ID" + ) + self.goal_id_entry = uiw.EntryHelp( + master=self.goal_general_frame, text="Unique ID of the goal." + ) + self.goal_type_label = uiw.uiLabel( + master=self.goal_general_frame, text="Type" + ) + self.goal_type_entry = uiw.EntryHelp( + master=self.goal_general_frame, + text="The type of the goal. This can only be specified when a new " + + "goal is created. If you want to change a goal's type, you will " + + "need to delete the goal and recreate it.", + ) + self.goal_type_entry.entry.config(state="disabled") + self.goal_msg_label = uiw.uiLabel( + master=self.goal_general_frame, text="Desc" + ) + self.goal_msg_textbox = uiw.uiTextbox(master=self.goal_general_frame) + self.goal_msg_textbox.configure(width=16, height=5, state=tk.NORMAL) + + self.goal_id_label.grid(row=0, column=0, sticky="ew") + self.goal_id_entry.frame.grid(row=0, column=1, sticky="ew") + self.goal_type_label.grid(row=1, column=0, sticky="ew") + self.goal_type_entry.frame.grid(row=1, column=1, sticky="ew") + self.goal_msg_label.grid(row=2, column=0, columnspan=2, sticky="ew") + self.goal_msg_textbox.grid(row=3, column=0, columnspan=2, sticky="ew") + + def build_items_touch_ui(self): + + self.IT_items_label = uiw.uiLabel( + master=self.goal_items_touch_frame, text="Items" + ) + + self.IT_items_listbox_var = tk.StringVar() + self.IT_items_listbox = uiw.uiListBox( + master=self.goal_items_touch_frame, + listvariable=self.IT_items_listbox_var, + selectmode="multiple", + ) + + self.IT_add_items_button = uiw.uiButton( + master=self.goal_items_touch_frame, + command=self.IT_add_items, + text="Add Item(s)", + ) + self.IT_remove_items_button = uiw.uiButton( + master=self.goal_items_touch_frame, + command=self.IT_remove_items, + text="Remove Item(s)", + ) + + self.IT_items_label.grid(row=0, column=0, columnspan=2, stick="ew") + self.IT_items_listbox.grid( + row=1, column=0, columnspan=2, sticky="nsew" + ) + self.IT_add_items_button.grid( + row=2, column=0, columnspan=2, sticky="ew" + ) + self.IT_remove_items_button.grid( + row=3, column=0, columnspan=2, sticky="ew" + ) + + def build_obj_items_touch_ui(self): + self.OIT_items_label = uiw.uiLabel( + master=self.goal_obj_items_touch_frame, text="Items" + ) + + self.OIT_items_listbox_var = tk.StringVar() + self.OIT_items_listbox = uiw.uiListBox( + master=self.goal_obj_items_touch_frame, + listvariable=self.OIT_items_listbox_var, + selectmode="multiple", + ) + + self.OIT_add_items_button = uiw.uiButton( + master=self.goal_obj_items_touch_frame, + command=self.OIT_add_items, + text="Add Item(s)", + ) + self.OIT_remove_items_button = uiw.uiButton( + master=self.goal_obj_items_touch_frame, + command=self.OIT_remove_items, + text="Remove Item(s)", + ) + + self.OIT_obj_label = uiw.uiLabel( + master=self.goal_obj_items_touch_frame, text="Object" + ) + + self.OIT_obj_entry = uiw.EntryHelp( + master=self.goal_obj_items_touch_frame, + text="The goal is complete when this object is in the same " + + "location as all items in the list above.\n\nNOTE: The value " + + "in this field can only be changed via the add/remove object " + + "buttons below.", + ) + self.OIT_obj_entry.entry.config(state="disabled") + + self.OIT_add_objs_button = uiw.uiButton( + master=self.goal_obj_items_touch_frame, + command=self.OIT_add_object, + text="Add Object(s)", + ) + self.OIT_remove_objs_button = uiw.uiButton( + master=self.goal_obj_items_touch_frame, + command=self.OIT_remove_objects, + text="Remove Object(s)", + ) + + self.OIT_items_label.grid(row=0, column=0, stick="ew") + self.OIT_items_listbox.grid( + row=1, column=0, columnspan=2, sticky="nsew" + ) + self.OIT_add_items_button.grid( + row=2, column=0, columnspan=2, sticky="ew" + ) + self.OIT_remove_items_button.grid( + row=3, column=0, columnspan=2, sticky="ew" + ) + self.OIT_obj_label.grid(row=4, column=0, sticky="ew") + self.OIT_obj_entry.frame.grid(row=4, column=1, sticky="ew") + self.OIT_add_objs_button.grid( + row=5, column=0, columnspan=2, sticky="ew" + ) + self.OIT_remove_objs_button.grid( + row=6, column=0, columnspan=2, sticky="ew" + ) + + def build_n_objs_destroyed_ui(self): + self.NOD_amount_label = uiw.uiLabel( + master=self.goal_n_objs_destroyed_frame, text="Amount (N)" + ) + self.NOD_amount_entry = uiw.EntryHelp( + master=self.goal_n_objs_destroyed_frame, + text="The number of the objects below that must be destroyed to " + + "complete this goal. Amount cannot be set to a value larger " + + "than the number of objects in the listbox below.", + ) + + self.NOD_objs_label = uiw.uiLabel( + master=self.goal_n_objs_destroyed_frame, text="Objects" + ) + + self.NOD_objs_listbox_var = tk.StringVar() + self.NOD_objs_listbox = uiw.uiListBox( + master=self.goal_n_objs_destroyed_frame, + listvariable=self.NOD_objs_listbox_var, + selectmode="multiple", + ) + self.NOD_add_objs_button = uiw.uiButton( + master=self.goal_n_objs_destroyed_frame, + command=self.NOD_add_objects, + text="Add Object(s)", + ) + self.NOD_remove_objs_button = uiw.uiButton( + master=self.goal_n_objs_destroyed_frame, + command=self.NOD_remove_objects, + text="Remove Object(s)", + ) + + self.NOD_amount_label.grid(row=0, column=0, stick="ew") + self.NOD_amount_entry.frame.grid(row=0, column=1, sticky="ew") + + self.NOD_objs_label.grid(row=1, column=0, columnspan=2, sticky="ew") + self.NOD_objs_listbox.grid( + row=2, column=0, columnspan=2, sticky="nsew" + ) + self.NOD_add_objs_button.grid( + row=3, column=0, columnspan=2, sticky="ew" + ) + self.NOD_remove_objs_button.grid( + row=4, column=0, columnspan=2, sticky="ew" + ) + + def build_n_objs_in_locs_ui(self): + self.NOIL_amount_label = uiw.uiLabel( + master=self.goal_n_objs_in_locs_frame, text="Amount (N)" + ) + self.NOIL_amount_entry = uiw.EntryHelp( + master=self.goal_n_objs_in_locs_frame, + text="The number of objects that must be in one of the goal " + + "locations." + ) + self.NOIL_objs_label = uiw.uiLabel( + master=self.goal_n_objs_in_locs_frame, text="Objects" + ) + self.NOIL_objs_listbox_var = tk.StringVar() + self.NOIL_objs_listbox = uiw.uiListBox( + master=self.goal_n_objs_in_locs_frame, + listvariable=self.NOIL_objs_listbox_var, + selectmode="multiple", + ) + self.NOIL_add_objs_button = uiw.uiButton( + master=self.goal_n_objs_in_locs_frame, + command=self.NOIL_add_objects, + text="Add Object(s)", + ) + self.NOIL_remove_objs_button = uiw.uiButton( + master=self.goal_n_objs_in_locs_frame, + command=self.NOIL_remove_objects, + text="Remove Object(s)", + ) + + self.NOIL_locs_label = uiw.uiLabel( + master=self.goal_n_objs_in_locs_frame, text="Locations" + ) + self.NOIL_locs_listbox_var = tk.StringVar() + self.NOIL_locs_listbox = uiw.uiListBox( + master=self.goal_n_objs_in_locs_frame, + listvariable=self.NOIL_locs_listbox_var, + selectmode="multiple", + ) + self.NOIL_add_locs_button = uiw.uiButton( + master=self.goal_n_objs_in_locs_frame, + command=self.NOIL_add_locations, + text="Add Locations(s)", + ) + self.NOIL_remove_locs_button = uiw.uiButton( + master=self.goal_n_objs_in_locs_frame, + command=self.NOIL_remove_locations, + text="Remove Locations(s)", + ) + + self.NOIL_amount_label.grid(row=0, column=0, sticky="ew") + self.NOIL_amount_entry.frame.grid(row=0, column=1, sticky="ew") + self.NOIL_objs_label.grid(row=1, column=0, columnspan=2, sticky="ew") + self.NOIL_objs_listbox.grid(row=2, column=0, columnspan=2, sticky="ew") + self.NOIL_add_objs_button.grid( + row=3, column=0, columnspan=2, sticky="ew" + ) + self.NOIL_remove_objs_button.grid( + row=4, column=0, columnspan=2, sticky="ew" + ) + self.NOIL_locs_label.grid(row=5, column=0, columnspan=2, sticky="ew") + self.NOIL_locs_listbox.grid(row=6, column=0, columnspan=2, sticky="ew") + self.NOIL_add_locs_button.grid( + row=7, column=0, columnspan=2, sticky="ew" + ) + self.NOIL_remove_locs_button.grid( + row=8, column=0, columnspan=2, sticky="ew" + ) + + def populate_goal_listbox(self, event=None): + + self.clear_all_fields() + + goal_type = self.select_goal_type_combo.get() + self.show_goal_type_attr_frame(goal_type) + + goal_data = self.ldr.get_gstate_templates() + keys = [] + + for goal in goal_data.values(): + if goal["type"] == goal_type: + keys.append(f"{goal['id']}") + + self.select_goal_listbox.delete(0, tk.END) + self.select_goal_listbox_var.set(keys) + + def show_goal_type_attr_frame(self, goal_type): + + next_attr_frame = self.current_goal_attribute_frame + + match (goal_type): + case "ITEMS_TOUCH": + next_attr_frame = self.goal_items_touch_frame + case "OBJ_ITEMS_TOUCH": + next_attr_frame = self.goal_obj_items_touch_frame + case "N_OBJS_DESTROYED": + next_attr_frame = self.goal_n_objs_destroyed_frame + case "N_OBJS_IN_LOCS": + next_attr_frame = self.goal_n_objs_in_locs_frame + + if self.current_goal_attribute_frame != next_attr_frame: + if self.current_goal_attribute_frame is not None: + self.current_goal_attribute_frame.pack_forget() + self.current_goal_attribute_frame = next_attr_frame + self.current_goal_attribute_frame.pack(side=tk.LEFT, fill=tk.BOTH) + + def show_goal(self, event=None): + selected_goal = self.get_currently_selected_goal() + if selected_goal is not None: + self.clear_all_fields() + + self.goal_id_entry.entry.insert(0, selected_goal["id"]) + self.goal_type_entry.entry.config(state="normal") + self.goal_type_entry.entry.insert(0, selected_goal["type"]) + self.goal_type_entry.entry.config(state="disabled") + self.goal_msg_textbox.insert(1.0, selected_goal["msg"]) + + match (selected_goal["type"]): + case "ITEMS_TOUCH": + self.show_items_touch(selected_goal) + case "OBJ_ITEMS_TOUCH": + self.show_obj_items_touch(selected_goal) + case "N_OBJS_DESTROYED": + self.show_n_objs_destroyed(selected_goal) + case "N_OBJS_IN_LOCS": + self.show_n_objs_in_locs(selected_goal) + + def show_items_touch(self, goal): + self.IT_items_listbox_var.set(goal["items"]) + + def show_obj_items_touch(self, goal): + self.OIT_items_listbox_var.set(goal["items"]) + self.OIT_obj_entry.entry.config(state="normal") + self.OIT_obj_entry.entry.insert(0, goal["object"]) + self.OIT_obj_entry.entry.config(state="disabled") + + def show_n_objs_destroyed(self, goal): + self.NOD_amount_entry.entry.insert(0, goal["amount"]) + self.NOD_objs_listbox_var.set(goal["objects"]) + + def show_n_objs_in_locs(self, goal): + self.NOIL_amount_entry.entry.insert(0, goal["amount"]) + self.NOIL_objs_listbox_var.set(goal["objects"]) + self.NOIL_locs_listbox_var.set(goal["locations"]) + + def clear_all_fields(self): + # General + self.goal_id_entry.entry.delete(0, tk.END) + self.goal_type_entry.entry.config(state="normal") + self.goal_type_entry.entry.delete(0, tk.END) + self.goal_type_entry.entry.config(state="disabled") + self.goal_msg_textbox.delete(1.0, tk.END) + # ITEMS_TOUCH + self.IT_items_listbox.delete(0, tk.END) + # OBJ_ITEMS_TOUCH + self.OIT_items_listbox.delete(0, tk.END) + self.OIT_obj_entry.entry.delete(0, tk.END) + # N_OBJS_DESTROYED + self.NOD_amount_entry.entry.delete(0, tk.END) + self.NOD_objs_listbox.delete(0, tk.END) + # N_OBJS_IN_LOCS + self.NOIL_amount_entry.entry.delete(0, tk.END) + self.NOIL_objs_listbox.delete(0, tk.END) + self.NOIL_locs_listbox.delete(0, tk.END) + + def get_currently_selected_goal(self): + goal_index = self.select_goal_listbox.curselection() + if len(goal_index) == 1: + goal_id = self.select_goal_listbox.get(goal_index[0]) + return self.ldr.get_gstate_template(goal_id) + else: + return None + + def select_goal_with_id(self, _id): + for index, goal_id in enumerate( + self.select_goal_listbox.get(0, tk.END) + ): + if _id == goal_id: + self.select_goal_listbox.selection_clear(0, tk.END) + self.select_goal_listbox.selection_set(index) + self.select_goal_listbox.activate(index) + self.show_goal() + break + + def update_goal(self): + """ + Updates the components JSON. values + """ + goal_data = self.ldr.get_gstate_templates() + selected_goal = self.get_currently_selected_goal() + if selected_goal is not None: + + # Update the ctype specific attributes + match selected_goal["type"]: + case "ITEMS_TOUCH": + self.update_items_touch(selected_goal) + case "OBJ_ITEMS_TOUCH": + self.update_obj_items_touch(selected_goal) + case "N_OBJS_DESTROYED": + self.update_n_objs_destroyed(selected_goal) + case "N_OBJS_IN_LOCS": + self.update_n_objs_in_locs(selected_goal) + + new_id = self.goal_id_entry.entry.get() + + # Update ID: must check if unique + if new_id != selected_goal["id"]: + if new_id in [x["id"] for x in goal_data]: + tk.messagebox.showwarning( + "Invalid goal ID", + f"WARNING: The goal id {new_id} already exists. " + + "Goal IDs must be unique.", + ) + elif len(new_id) == 0: + tk.messagebox.showwarning( + "Invalid goal ID", "Goal IDs cannot be empty." + ) + else: + old_id = selected_goal["id"] + selected_goal["id"] = new_id + del goal_data[old_id] + goal_data[new_id] = selected_goal + + selected_goal["msg"] = self.goal_msg_textbox.get(1.0, tk.END) + + self.populate_goal_listbox(selected_goal["id"]) + self.select_goal_with_id(selected_goal["id"]) + + def update_items_touch(self, selected_goal): + items = self.IT_items_listbox.get(0, tk.END) + if len(items) < 2: + tk.messagebox.showwarning( + "Too Few Items", "This goal requires at least two items." + ) + else: + selected_goal["items"] = items + + def update_obj_items_touch(self, selected_goal): + items = self.OIT_items_listbox.get(0, tk.END) + obj = self.OIT_obj_entry.entry.get() + if len(items) == 0: + tk.messagebox.showwarning( + "Too Few Items", "This goal requires at least one item." + ) + elif len(obj) == 0: + tk.messagebox.showwarning( + "Object cannot be empty", + "This goal requires at least one object." + ) + else: + selected_goal["items"] = items + selected_goal["object"] = obj + + def update_n_objs_destroyed(self, selected_goal): + amount = self.NOD_amount_entry.entry.get() + objs = self.NOD_objs_listbox.get(0, tk.END) + try: + amount = int(amount) + if amount <= 0: + raise Exception + except Exception: + tk.messagebox.showwarning( + "Invalid Amount", "Amount must be a positive integer." + ) + + if amount > len(objs): + tk.messagebox.showwarning( + "Amount Too Large", + "The value in the amount field cannot be larger than the " + + "number of objects in the listbox.", + ) + elif len(objs) == 0: + tk.messagebox.showwarning( + "Too Few Objects", "This goal must have at least one object." + ) + else: + selected_goal["amount"] = amount + selected_goal["objects"] = objs + + def update_n_objs_in_locs(self, selected_goal): + amount = self.NOIL_amount_entry.entry.get() + objs = self.NOIL_objs_listbox.get(0, tk.END) + locs = self.NOIL_locs_listbox.get(0, tk.END) + + try: + amount = int(amount) + if amount <= 0: + raise Exception + except Exception: + tk.messagebox.showwarning( + "Invalid Amount", "Amount must be a positive integer." + ) + if amount > len(objs): + tk.messagebox.showwarning( + "Invalid Amount", + "The value in amount cannot be larger than the number of " + + "objects.", + ) + elif amount > len(locs): + tk.messagebox.showwarning( + "Invalid Amount", + "The value in amount cannot be larger than the number of " + + "locations.", + ) + else: + selected_goal["objects"] = objs + selected_goal["locations"] = locs + selected_goal["amount"] = amount + + def new_goal(self): + """ + Creates a new goal template and adds it to the goal dictionary. + """ + + goal_data = self.ldr.get_gstate_templates() + + good = False + while not good: + + dialog = UINewGoal() + result = dialog.get_result() + + if result["type"] in gstate.GSTATE_TYPES: + if len(result["id"]) > 0 and result["id"] not in [ + x["id"] for x in goal_data.values() + ]: + good = True + else: + tk.messagebox.showwarning( + "Warning", "A goal must have a non-empty, unique ID." + ) + else: + tk.messagebox.showwarning( + "Warning", + "A goal must have a valid type." + ) + + goal_attrs = gstate.GOAL_ATTRS_BY_TYPE[result["type"]] + + for attr, val in goal_attrs: + result[attr] = val + + goal_data.update({result["id"]: result}) + + self.populate_goal_listbox() + + def delete_goal(self): + """ + Deletes the currently selected component from the JSON and component + dictionary. + """ + + goal_data = self.ldr.get_gstate_templates() + + selected_goal = self.get_currently_selected_goal() + if selected_goal is not None: + del goal_data[selected_goal["id"]] + self.populate_goal_listbox() + + def IT_add_items(self, event=None): + dialog = UIGetItems(self.ldr, "multiple") + results = dialog.get_result() + if results is not None: + for result in results: + self.IT_items_listbox.insert(tk.END, result) + + def IT_remove_items(self, event=None): + selections = self.IT_items_listbox.curselection() + for s in reversed(selections): + self.IT_items_listbox.delete(s) + + def OIT_add_items(self, event=None): + dialog = UIGetItems(self.ldr, "multiple") + results = dialog.get_result() + if results is not None: + for result in results: + self.OIT_items_listbox.insert(tk.END, result) + + def OIT_remove_items(self, event=None): + selections = self.OIT_items_listbox.curselection() + for s in reversed(selections): + self.OIT_items_listbox.delete(s) + + def OIT_add_object(self, event=None): + dialog = UIGetObjects(self.ldr, "single") + result = dialog.get_result() + if result is not None and len(result) == 1: + self.OIT_obj_entry.entry.config(state="normal") + self.OIT_obj_entry.entry.insert(0, result[0]) + self.OIT_obj_entry.entry.config(state="disabled") + + def OIT_remove_objects(self, event=None): + self.OIT_obj_entry.entry.config(state="normal") + self.OIT_obj_entry.entry.insert(0, "") + self.OIT_obj_entry.entry.config(state="disabled") + + def NOD_add_objects(self, event=None): + dialog = UIGetObjects(self.ldr, "multiple") + results = dialog.get_result() + if results is not None: + for result in results: + self.NOD_objs_listbox.insert(tk.END, result) + + def NOD_remove_objects(self, event=None): + selections = self.NOD_objs_listbox.curselection() + for s in reversed(selections): + self.NOD_objs_listbox.delete(s) + + def NOIL_add_objects(self, event=None): + dialog = UIGetObjects(self.ldr, "multiple") + results = dialog.get_result() + if results is not None: + for result in results: + self.NOIL_objs_listbox.insert(tk.END, result) + + def NOIL_remove_objects(self, event=None): + selections = self.NOIL_objs_listbox.curselection() + for s in reversed(selections): + self.NOIL_objs_listbox.delete(s) + + def NOIL_add_locations(self, event=None): + new_loc = tk.simpledialog.askstring( + "New Goal Location", + "Enter a new x,y location in the format: x,y\n\nValues for x " + + "and y must be integers. They should be valid, reachable " + + "locations for the intended map (not verified by the system).", + ) + xy = new_loc.split(",") + if len(xy) == 2 and xy[0].isdigit() and xy[1].isdigit(): + # Cast to int to remove beginning zeros or plus sign. + # not that anyone will do this. + x = int(xy[0]) + y = int(xy[1]) + # prevent negatives + if x >= 0 and y >= 0: + self.NOIL_locs_listbox.insert(tk.END, f"{x},{y}") + + def NOIL_remove_locations(self, event=None): + selections = self.NOIL_locs_listbox.curselection() + for s in reversed(selections): + self.NOIL_locs_listbox.delete(s) + + def goto_home(self): + self.controller.show_frame("home_page") + + def save_to_json(self): + self.ldr.save_gstate_templates() diff --git a/ui_homepage.py b/ui_homepage.py index 636e6a9..8906d4f 100644 --- a/ui_homepage.py +++ b/ui_homepage.py @@ -1,23 +1,10 @@ import tkinter as tk -from tkinter import messagebox -from tkinter.font import Font -import tkinter.scrolledtext as scrolltext -import importlib.util -import sys -import os import queue -import logging - +from PIL import ImageTk, Image import sim import loader -import ui_sim import msgs -from zexceptions import * -from ui_widgets import * -import ui_about -import main -from main import * -import ui_advanced_config +import ui_widgets as uiw class UIHomepage(tk.Frame): @@ -46,29 +33,90 @@ def build_ui(self): places adv config button, places about button """ # self.mainFrame = uiQuietFrame(master=self) - self.maia_label = uiLabel(master=self, text="Maine AI Arena") - self.maia_label.pack(side=tk.TOP, fill=tk.BOTH, expand=True) - self.start_game_button = uiButton( - master=self, - text="Start Game", - command=lambda: self.controller.show_frame("setup_page"), + self.maia_image = ImageTk.PhotoImage(Image.open("images/maia.png")) + self.maia_image_label = uiw.uiLabel( + master=self, image=self.maia_image + ) # text="Maine AI Arena") + self.maia_image_label.pack(side=tk.TOP, fill="x") + self.maia_text_label = uiw.uiLabel( + master=self, text="Maine AI Arena", font=("Arial", 24) ) - self.start_game_button.config(width=400) - self.start_game_button.pack(side=tk.BOTTOM, fill="y", expand=True) + self.maia_text_label.pack(side=tk.TOP, fill="x") - self.adv_config_button = uiButton( - master=self, - command=lambda: ui_advanced_config.UISettings(self, self.logger), - text="Advanced Config", + self.general_frame = tk.LabelFrame( + self, text="General", labelanchor="n", font=("Arial", 15) + ) + self.config_frame = tk.LabelFrame( + self, text="Config", labelanchor="n", font=("Arial", 15) ) - self.adv_config_button.config(width=400) - self.adv_config_button.pack(side=tk.BOTTOM, fill="y", expand=True) + self.match_frame = tk.LabelFrame( + self, text="Match", labelanchor="n", font=("Arial", 15) + ) + self.general_frame.pack(side=tk.TOP) + self.config_frame.pack(side=tk.TOP) + self.match_frame.pack(side=tk.TOP) - self.about_button = uiButton( - master=self, + self.about_button = uiw.uiButton( + master=self.general_frame, text="About MAIA", command=lambda: self.controller.show_frame("about_page"), ) - self.about_button.config(width=400) - self.about_button.pack(side=tk.BOTTOM, fill="y", expand=True) + self.about_button.config(width=20, height=3) + self.about_button.grid(row=0, column=0, sticky="ew") + + self.config_teams_button = uiw.uiButton( + master=self.config_frame, + command=lambda: self.controller.show_frame("config_team"), + text="Team Config", + ) + self.config_teams_button.config(width=20, height=3) + self.config_teams_button.grid(row=0, column=0, sticky="ew") + + self.config_component_button = uiw.uiButton( + master=self.config_frame, + command=lambda: self.controller.show_frame("config_component"), + text="Component Config", + ) + self.config_component_button.config(width=20, height=3) + self.config_component_button.grid(row=0, column=1, sticky="ew") + + self.config_object_button = uiw.uiButton( + master=self.config_frame, + command=lambda: self.controller.show_frame("config_object"), + text="Object Config", + ) + self.config_object_button.config(width=20, height=3) + self.config_object_button.grid(row=1, column=0, sticky="ew") + + self.config_item_button = uiw.uiButton( + master=self.config_frame, + command=lambda: self.controller.show_frame("config_item"), + text="Item Config", + ) + self.config_item_button.config(width=20, height=3) + self.config_item_button.grid(row=1, column=1, sticky="ew") + + self.config_map_button = uiw.uiButton( + master=self.config_frame, + command=lambda: self.controller.show_frame("config_map"), + text="Map Config", + ) + self.config_map_button.config(width=20, height=3) + self.config_map_button.grid(row=2, column=0, sticky="ew") + + self.config_gstate_button = uiw.uiButton( + master=self.config_frame, + command=lambda: self.controller.show_frame("config_gstate"), + text="Goal Config", + ) + self.config_gstate_button.config(width=20, height=3) + self.config_gstate_button.grid(row=2, column=1, sticky="we") + + self.start_game_button = uiw.uiButton( + master=self.match_frame, + text="Match Setup", + command=lambda: self.controller.show_frame("setup_page"), + ) + self.start_game_button.config(width=20, height=3) + self.start_game_button.pack(side=tk.TOP) diff --git a/ui_item_config.py b/ui_item_config.py new file mode 100644 index 0000000..9576a04 --- /dev/null +++ b/ui_item_config.py @@ -0,0 +1,332 @@ +import tkinter as tk +import ui_widgets as uiw + + +class UIItemConfig(tk.Frame): + def __init__(self, controller, ldr, master=None, logger=None): + super().__init__(master) + self.controller = controller + self.master = master + self.configure(bg=uiw.BGCOLOR) + self.logger = logger + self.ldr = ldr + self.build_ui() + self.ui_map = None + + def validate_number_entry(self, input): + """ + Validates each entered value (input) to ensure it is a number. + """ + input.replace(".", "", 1) + input.replace("-", "", 1) + if input.isdigit() or input == "" or "-" in input or "." in input: + return True + + else: + return False + + def get_focused_entry(self): + """ + Returns the currently focused entry in advanced config. + """ + focused_entry = self.focus_get() + return focused_entry + + def build_ui(self): + """ + Initializes all widgets and places them. + """ + # Make main frames + + self.main_frame = uiw.uiQuietFrame(master=self) + self.item_selection_column = uiw.uiLabelFrame( + master=self.main_frame, text="Items" + ) + self.items_column = uiw.uiLabelFrame( + master=self.main_frame, text="Item Info" + ) + self.button_row = uiw.uiQuietFrame(master=self.main_frame) + self.title_label = uiw.uiLabel( + master=self.main_frame, text="Item Config" + ) + self.validate_num = self.main_frame.register( + self.validate_number_entry + ) + + # Place frames + self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + self.title_label.pack(side=tk.TOP, fill="x") + self.button_row.pack(side=tk.BOTTOM, fill="x") + self.item_selection_column.pack(side=tk.LEFT, fill="y") + self.items_column.pack(side=tk.LEFT, fill="y") + + # Item Selection Widgets + self.select_item_listbox_var = tk.StringVar() + self.select_item_listbox = uiw.uiListBox( + master=self.item_selection_column, + listvariable=self.select_item_listbox_var, + selectmode="browse", + ) + self.select_item_listbox.bind("<>", self.show_item_info) + self.select_item_listbox.pack(side=tk.LEFT, fill=tk.BOTH) + + # Item Info Widgets + self.item_id_label = uiw.uiLabel(master=self.items_column, text="ID") + self.item_id_entry = uiw.EntryHelp( + master=self.items_column, text="The unique ID of the item." + ) + self.item_name_label = uiw.uiLabel( + master=self.items_column, text="Name" + ) + self.item_name_entry = uiw.EntryHelp( + master=self.items_column, text="The name of the item." + ) + self.item_weight_label = uiw.uiLabel( + master=self.items_column, text="Weight" + ) + self.item_weight_entry = uiw.EntryHelp( + master=self.items_column, + text="The weight of the item. Weight (and bulk) are used to " + + "determine whether an object (with the correct component) can " + + "pick up an item." + ) + self.item_bulk_label = uiw.uiLabel( + master=self.items_column, text="Bulk" + ) + self.item_bulk_entry = uiw.EntryHelp( + master=self.items_column, + text="The bulk (an abstract measure of side) of the item. " + + "Bulk (and weight) are used to determine whether an object " + + "(with the correct component) can pick up an item." + ) + self.item_sprite_label = uiw.uiLabel( + master=self.items_column, text="Sprite Filename" + ) + self.item_sprite_entry = uiw.EntryHelp( + master=self.items_column, + text="The filename of the sprite used to display the item on " + + "the map." + ) + + self.item_id_label.grid(row=0, column=0, sticky="ew") + self.item_id_entry.frame.grid(row=0, column=1, sticky="ew") + self.item_name_label.grid(row=1, column=0, sticky="ew") + self.item_name_entry.frame.grid(row=1, column=1, sticky="ew") + self.item_weight_label.grid(row=2, column=0, sticky="ew") + self.item_weight_entry.frame.grid(row=2, column=1, sticky="ew") + self.item_bulk_label.grid(row=3, column=0, sticky="ew") + self.item_bulk_entry.frame.grid(row=3, column=1, sticky="ew") + self.item_sprite_label.grid(row=4, column=0, sticky="ew") + self.item_sprite_entry.frame.grid(row=4, column=1, sticky="ew") + + # Team Buttons + + self.new_item_button = uiw.uiButton( + master=self.button_row, + command=self.create_item, + text="Create Item" + ) + self.new_item_button.pack(side=tk.LEFT) + self.update_item_button = uiw.uiButton( + master=self.button_row, + command=self.update_item, + text="Update Item" + ) + self.update_item_button.pack(side=tk.LEFT) + self.delete_item_button = uiw.uiCarefulButton( + master=self.button_row, + command=self.delete_item, + text="Delete Item" + ) + self.delete_item_button.pack(side=tk.LEFT) + + # High-level Buttons + self.home_button = uiw.uiButton( + master=self.button_row, command=self.goto_home, text="Home" + ) + self.home_button.pack(side=tk.RIGHT) + self.save_to_json_button = uiw.uiCarefulButton( + master=self.button_row, + command=self.save_to_json, + text="Save Items to JSON" + ) + self.save_to_json_button.pack(side=tk.RIGHT) + + self.populate_item_listbox() + + def populate_item_listbox(self): + """ + Gets information from the loader and assigns current values for each + setting type. + """ + self.clear_item_info() + + item_data = self.ldr.get_item_templates() + + item_entries = [] + for itm in item_data.values(): + entry = f"{itm['id']}:{itm['name']}" + item_entries.append(entry) + + self.select_item_listbox.delete(0, tk.END) + self.select_item_listbox_var.set(item_entries) + + def clear_item_info(self): + self.item_id_entry.entry.delete(0, tk.END) + self.item_name_entry.entry.delete(0, tk.END) + self.item_weight_entry.entry.delete(0, tk.END) + self.item_bulk_entry.entry.delete(0, tk.END) + self.item_sprite_entry.entry.delete(0, tk.END) + + def show_item_info(self, event=None): + """ + Updates the values stored in the item entry widgets. + """ + self.clear_item_info() + cur_item = self.get_currently_selected_item() + if cur_item is not None: + + self.item_id_entry.entry.insert(0, cur_item["id"]) + self.item_name_entry.entry.insert(0, cur_item["name"]) + self.item_weight_entry.entry.insert(0, cur_item["weight"]) + self.item_bulk_entry.entry.insert(0, cur_item["bulk"]) + self.item_sprite_entry.entry.insert(0, cur_item["sprite_filename"]) + + # CREATE NEW + def create_item(self): + """ + Creates a new item and adds it to the item dictionary. + """ + + item_data = self.ldr.get_item_templates() + + good_id = False + while not good_id: + item_id = tk.simpledialog.askstring( + "New Item ID", "Please enter an ID for the new item." + ) + if item_id is None: + break + elif len(item_id) == 0: + tk.messagebox.showwarning( + "Invalid Item ID", "You must enter an item ID to continue" + ) + elif item_id in item_data.keys(): + tk.messagebox.showwarning( + "Invalid Item ID", + "This ID already exists, please enter a new ID." + ) + else: + good_id = True + + new_item_data = { + "id": item_id, + "name": "", + "weight": 0, + "bulk": 0, + "sprite_filename": "", + } + item_data.update({item_id: new_item_data}) + self.populate_item_listbox() + self.select_item(item_id) + + def select_item(self, _id): + for i, entry in enumerate(self.select_item_listbox.get(0, tk.END)): + item_id, item_name = entry.split(":") + if item_id == _id: + self.select_item_listbox.selection_clear(0, tk.END) + self.select_item_listbox.selection_set(i) + self.select_item_listbox.activate(i) + self.show_item_info() + break + + # UPDATE JSON FILES + def update_item(self): + """ + Updates the item's data with the loader. + """ + item_data = self.ldr.get_item_templates() + cur_item = self.get_currently_selected_item() + if cur_item is not None: + + # ID + new_id = self.item_id_entry.entry.get() + if new_id != cur_item["id"]: + if len(new_id) == 0: + tk.messagebox.showwarning( + "Invalid Item ID", "An item ID cannot be empty." + ) + elif new_id in item_data: + tk.messagebox.showwarning( + "Invalid Item ID", + "The new ID already exists. Item IDs must be unique.", + ) + else: + old_id = cur_item["id"] + cur_item["id"] = new_id + del item_data[old_id] + item_data[new_id] = cur_item + + # NAME + new_name = self.item_name_entry.entry.get() + if len(new_name) == 0: + tk.messagebox.showwarning( + "Invalid Item Name", "Item names cannot be empty." + ) + else: + cur_item["name"] = new_name + + # WEIGHT + new_weight = self.item_weight_entry.entry.get() + try: + new_weight = int(new_weight) + cur_item["weight"] = new_weight + except Exception: + tk.messagebox.showwarning( + "Invalid Item Weight", + "The current weight cannot be cast to an integer.", + ) + + # BULK + new_bulk = self.item_bulk_entry.entry.get() + try: + new_bulk = int(new_bulk) + cur_item["bulk"] = new_bulk + except Exception: + tk.messagebox.showwarning( + "Invalid Item Bulk", + "The current bulk cannot be cast to an integer.", + ) + + # SPRITE: no checks + new_sprite = self.item_sprite_entry.entry.get() + cur_item["sprite_filename"] = new_sprite + + self.populate_item_listbox() + + # DELETE + + def delete_item(self): + """ + Deletes the currently selected team from the JSON and team dictionary. + """ + item_data = self.ldr.get_item_templates() + cur_item = self.get_currently_selected_item() + if cur_item is not None: + del item_data[cur_item["id"]] + self.populate_item_listbox() + + def get_currently_selected_item(self): + item_index = self.select_item_listbox.curselection() + if len(item_index) == 1: + item_entry = self.select_item_listbox.get(item_index[0]) + item_id, item_name = item_entry.split(":") + return self.ldr.get_item_template(item_id) + else: + return None + + def save_to_json(self): + self.ldr.save_item_templates() + + def goto_home(self): + self.controller.show_frame("home_page") diff --git a/ui_map_config.py b/ui_map_config.py index 2daf1c2..d1341ce 100644 --- a/ui_map_config.py +++ b/ui_map_config.py @@ -1,35 +1,52 @@ import tkinter as tk -from tkinter.font import Font -import tkinter.scrolledtext as scrolltext +import tkinter.font +import enum +import zfunctions +import ui_widgets as uiw -import uuid -import loader -from ui_widgets import * +class AddRemove(enum.Enum): + Info = 0 + AddObject = 1 + DelObject = 2 + AddItem = 3 + DelItem = 4 + AddStart = 5 + DelStart = 6 -class UIMapConfig(tk.Toplevel): - def __init__(self, _map, master=None, logger=None): +class UIMapConfig(tk.Frame): + def __init__(self, controller, ldr, master=None, logger=None): """Sets window and frame information and calls function to build ui""" super().__init__(master) + self.controller = controller self.master = master self.logger = logger - self.ldr = loader.Loader(self.logger) + self.ldr = ldr - self.map = _map - self.map_width = self.map.get_data("width") - self.map_height = self.map.get_data("height") + self.add_remove_var = AddRemove.Info + + # Possible general config parameters. + self.cell_size = 32 # in pixels + + # in pixels, the area around the map where row/column + # labels are drawn. + self.border_size = 32 + self.map_obj_char_size = 24 # font size + self.map_item_char_size = 10 # font size + + self.canvas = None + self.x_bar = None + self.y_bar = None self.objs = {} self.items = {} self.sides = {} - self.container = uiQuietFrame(master=self) - self.container.pack(fill=tk.BOTH, expand=True, side=tk.TOP) - self.grid_rowconfigure(0, weight=1) - self.grid_columnconfigure(0, weight=1) - self.build_ui() + self.populate_map_listbox() + self.populate_obj_listbox() + self.populate_item_listbox() def build_ui(self): """Generates UI of map @@ -38,209 +55,1162 @@ def build_ui(self): draws placed objects, draws placed items, draws ai-controlled objects """ - self.cell_size = 32 - self.map_obj_char_size = 24 - self.map_item_char_size = 10 - self.char_offset = (self.cell_size - self.map_obj_char_size) / 2 - self.map_obj_font = tk.font.Font( - family="TkFixedFont", size=self.map_obj_char_size - ) - self.map_item_font = tk.font.Font( - family="TkFixedFont", size=self.map_item_char_size - ) - - self.x_bar = tk.Scrollbar(self.container, orient=tk.HORIZONTAL) - self.y_bar = tk.Scrollbar(self.container, orient=tk.VERTICAL) - self.canvas = uiCanvas( - master=self.container, - width=800, - height=800, - xscrollcommand=self.x_bar.set, - yscrollcommand=self.y_bar.set, - scrollregion=( - 0, - 0, - (self.map_width + 2) * self.cell_size, - (self.map_height + 2) * self.cell_size, - ), - cell_size=self.cell_size, - obj_char_size=self.map_obj_char_size, - item_char_size=self.map_item_char_size, - char_offset=self.char_offset, - obj_font=self.map_obj_font, - item_font=self.map_item_font, - ) - self.y_bar.configure(command=self.canvas.yview) - self.x_bar.configure(command=self.canvas.xview) - self.y_bar.pack(side=tk.RIGHT, fill=tk.Y) - self.x_bar.pack(side=tk.BOTTOM, fill=tk.X) - self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) - - self.canvas.yview_moveto(0.0) - self.canvas.xview_moveto(0.0) - - self.add_objects() - self.add_ai_objects() - self.add_items() - - # Draw the background tiles - self.canvas_background_tile_ids = [] - self.canvas_background_RCnum_ids = [] - self.draw_tiles() - - self.obj_drawIDs = {} - self.init_objects() - - self.item_drawIDs = {} - self.init_items() - - def add_objects(self): - """Adds edges and placed object to obj list""" - # Create the map border - edge_obj_id = self.map.get_data("edge_obj_id") - edge_coords = self.map.get_list_of_edge_coordinates() - for ec in edge_coords: - # Copy the obj - new_obj = self.ldr.copy_obj_template(edge_obj_id) - # Create obj place data - data = {} - data["x"] = ec[0] - data["y"] = ec[1] - data["uuid"] = uuid.uuid4() - # Place, add to objDict and add to map - new_obj.place(data) - self.objs[data["uuid"]] = new_obj - - # Add all placed objects - pl_objs = self.map.get_data("placed_objects") - for oid, lst in pl_objs.items(): - for o in lst: - # If an object entry in placed_objs does not - # have a position, it is ignored. - if "x" in o and "y" in o: - new_obj = self.ldr.copy_obj_template(oid) - data = o - data["uuid"] = uuid.uuid4() - new_obj.place(data) - self.objs[data["uuid"]] = new_obj - - def add_ai_objects(self): - """Adds ai-controlled object to obj list""" - # Add possible team and ai-controlled obj locations - sides = self.map.get_data("sides") - for iid, lst in sides.items(): - starting_locations = list(lst["starting_locations"]) - - for loc in starting_locations: - new_obj = self.ldr.copy_obj_template("start_loc") - new_obj.set_data("fill_alive", lst["color"]) - data = {} - - data["x"] = loc[0] - data["y"] = loc[1] - data["uuid"] = uuid.uuid4() - - # Place and store and add to map - new_obj.place(data) - self.objs[data["uuid"]] = new_obj - - def add_items(self): - """Adds placed items to item list""" - # Add all placed items - pl_items = self.map.get_data("placed_items") - for iid, lst in pl_items.items(): - for i in lst: - # If an item entry does not have a position, ignore. - if "x" in i and "y" in i: - new_item = self.ldr.copy_item_template(iid) - data = i - data["uuid"] = uuid.uuid4() - new_item.place(data) - self.items[data["uuid"]] = new_item - - def draw_tiles(self): - """Draws tiles on canvas""" - w = self.map_width + 2 - h = self.map_height + 2 + self.main_frame = uiw.uiQuietFrame(master=self) + self.left_frame = uiw.uiScrollFrame(master=self.main_frame) + self.map_selection_frame = uiw.uiLabelFrame( + master=self.left_frame.sub_frame, text="Maps" + ) + self.map_container_frame = uiw.uiQuietFrame(master=self.main_frame) + self.map_frame = uiw.uiQuietFrame(master=self.map_container_frame) + # self.map_data_frame = uiScrollFrame(master=self.left_frame) + self.map_info_frame = uiw.uiLabelFrame( + master=self.left_frame.sub_frame, text="Map Info" + ) + self.side_frame = uiw.uiLabelFrame( + master=self.left_frame.sub_frame, text="Sides" + ) + self.paint_frame = uiw.uiQuietFrame(master=self.main_frame) + self.add_remove_frame = uiw.uiQuietFrame(master=self.paint_frame) + self.obj_selection_frame = uiw.uiLabelFrame( + master=self.paint_frame, text="Objects" + ) + self.item_selection_frame = uiw.uiLabelFrame( + master=self.paint_frame, text="Items" + ) + self.button_row = uiw.uiQuietFrame(master=self.main_frame) + self.title_label = uiw.uiLabel( + master=self.main_frame, text="Map Configuration" + ) + + self.main_frame.pack(fill=tk.BOTH, expand=True) + self.title_label.pack(side=tk.TOP, fill="x") + self.button_row.pack(side=tk.BOTTOM, fill="x") + self.left_frame.pack(side=tk.LEFT, fill="y", expand=False) + self.map_container_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + # self.map_data_frame.pack(side=tk.TOP, fill="y") + self.map_selection_frame.pack(side=tk.TOP, fill="y") + self.map_selection_frame.rowconfigure(0, weight=1) + self.map_info_frame.pack(side=tk.TOP, fill="y") + self.map_info_frame.rowconfigure(0, weight=1) + self.side_frame.pack(side=tk.TOP, fill="y") + self.side_frame.rowconfigure(0, weight=1) + self.map_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + self.paint_frame.pack(side=tk.RIGHT, fill="y", before=self.map_frame) + self.add_remove_frame.pack(side=tk.TOP, fill="x") + self.add_remove_frame.rowconfigure(0, weight=1) + self.add_remove_frame.columnconfigure(0, weight=1) + self.obj_selection_frame.pack(side=tk.TOP, fill="y", expand=True) + self.obj_selection_frame.rowconfigure(0, weight=1) + self.item_selection_frame.pack(side=tk.TOP, fill="y", expand=True) + self.item_selection_frame.rowconfigure(0, weight=1) + + # MAP SELECTION + self.map_select_listbox_var = tk.StringVar() + self.map_select_listbox = uiw.uiListBox( + master=self.map_selection_frame, + listvariable=self.map_select_listbox_var, + selectmode="single", + ) + self.map_select_listbox.grid(row=0, column=0, sticky="ew") + self.map_select_listbox.bind("<>", self.show_map) + + # MAP INFO + # self.map_info_frame.columnconfigure(1,weight=1) + + self.map_id_label = uiw.uiLabel(master=self.map_info_frame, text="ID") + self.map_id_entry = uiw.EntryHelp( + master=self.map_info_frame, text="The unique ID of the map." + ) + self.map_name_label = uiw.uiLabel( + master=self.map_info_frame, text="Name" + ) + self.map_name_entry = uiw.EntryHelp( + master=self.map_info_frame, + text="The name of the map (does not have to be unique).", + ) + self.map_desc_label = uiw.uiLabel( + master=self.map_info_frame, text="Description" + ) + self.map_desc_text = uiw.uiTextbox(master=self.map_info_frame) + self.map_desc_text.configure(width=16, height=5, state=tk.NORMAL) + self.map_width_label = uiw.uiLabel( + master=self.map_info_frame, text="Width" + ) + self.map_width_entry = uiw.EntryHelp( + master=self.map_info_frame, + text="The playable width of the map in number of cells. NOTE: " + + "The actual width will be two greater to compensate for the " + + "edges." + ) + self.map_height_label = uiw.uiLabel( + master=self.map_info_frame, text="Height" + ) + self.map_height_entry = uiw.EntryHelp( + master=self.map_info_frame, + text="The playable height of the map in number of cells. NOTE: " + + "The actual width will be two greater to compensate for the " + + "edges." + ) + self.map_edge_id_label = uiw.uiLabel( + master=self.map_info_frame, text="Edge Obj ID" + ) + self.map_edge_id_entry = uiw.EntryHelp( + master=self.map_info_frame, + text="The ID of the object at the outer edge of the map. " + + "This should be an indestructible object. Copies of this object " + + "will be added automatically to fence in the playable area." + ) + + self.map_id_label.grid(row=0, column=0, sticky="ew") + self.map_id_entry.frame.grid(row=0, column=1, sticky="ew") + self.map_name_label.grid(row=1, column=0, sticky="ew") + self.map_name_entry.frame.grid(row=1, column=1, sticky="ew") + self.map_width_label.grid(row=2, column=0, sticky="ew") + self.map_width_entry.frame.grid(row=2, column=1, sticky="ew") + self.map_height_label.grid(row=3, column=0, sticky="ew") + self.map_height_entry.frame.grid(row=3, column=1, sticky="ew") + self.map_desc_label.grid(row=4, column=0, columnspan=2, sticky="ew") + self.map_desc_text.grid(row=5, column=0, columnspan=2, sticky="ew") + self.map_edge_id_label.grid(row=6, column=0, sticky="ew") + self.map_edge_id_entry.frame.grid(row=6, column=1, sticky="ew") + + # SIDE INFO + # self.side_frame.columnconfigure(0,weight=1) + self.side_select_listbox_var = tk.StringVar() + self.side_select_listbox = uiw.uiListBox( + master=self.side_frame, + listvariable=self.side_select_listbox_var, + selectmode="single", + ) + self.side_select_listbox.bind("<>", self.show_side) + + self.side_id_label = uiw.uiLabel(master=self.side_frame, text="ID") + self.side_id_entry = uiw.EntryHelp( + master=self.side_frame, + text="A side is a placeholder for a team of agents. Each side " + + "must have a unique ID.", + ) + self.side_name_label = uiw.uiLabel(master=self.side_frame, text="Name") + self.side_name_entry = uiw.EntryHelp( + master=self.side_frame, + text="The name of the side. Names are used to distinguish sides " + + "during match setup.", + ) + self.side_num_agents_label = uiw.uiLabel( + master=self.side_frame, text="Num. Agents" + ) + self.side_num_agents_entry = uiw.EntryHelp( + master=self.side_frame, + text="The number of agents a team must have. Team size cannot " + + "be larger than the number of starting locations. It can be " + + "less a asymmetric match is desired or if random placement is " + + "enabled.", + ) + self.side_color_label = uiw.uiLabel( + master=self.side_frame, text="Color" + ) + self.side_color_entry = uiw.EntryHelp( + master=self.side_frame, + text="The placement token color. This is only used on this " + + "configuration screen to denote team ownership of agent " + + "placement tokens. It will not recolor sprites. NOTE: colors " + + "must be given as hex RGB values (e.g. #FFFFFF) or as a " + + "recognized tkinter named color. Color strings are NOT " + + "currently validated by MAIA.", + ) + self.side_random_label = uiw.uiLabel( + master=self.side_frame, text="Random" + ) + self.side_random_entry = uiw.EntryHelp( + master=self.side_frame, + text="Are agents randomly assigned starting locations? " + + "If 'true', agents will be randomly distributed over the " + + "starting locations. If 'false, starting location is determined " + + "by order. true, t, 1, yes, y eval to true. Everything else is " + + "false.", + ) + self.side_gstate_label = uiw.uiLabel( + master=self.side_frame, text="Goal State" + ) + self.side_gstate_combo_var = tk.StringVar() + self.side_gstate_combo = uiw.ComboBoxHelp( + master=self.side_frame, + textvariable=self.side_gstate_combo_var, + text="The goal state defines the 'win' condition.", + ) + gstate_ids = [] + gstate_data = self.ldr.get_gstate_templates() + for gstate in gstate_data.values(): + gstate_ids.append(gstate["id"]) + self.side_gstate_combo.combobox.configure(values=gstate_ids) + + self.side_select_listbox.grid( + row=0, column=0, columnspan=2, sticky="ew" + ) + self.side_id_label.grid(row=1, column=0, sticky="ew") + self.side_id_entry.frame.grid(row=1, column=1, sticky="ew") + self.side_name_label.grid(row=2, column=0, sticky="ew") + self.side_name_entry.frame.grid(row=2, column=1, sticky="ew") + self.side_num_agents_label.grid(row=3, column=0, sticky="ew") + self.side_num_agents_entry.frame.grid(row=3, column=1, sticky="ew") + self.side_color_label.grid(row=4, column=0, sticky="ew") + self.side_color_entry.frame.grid(row=4, column=1, sticky="ew") + self.side_random_label.grid(row=5, column=0, sticky="ew") + self.side_random_entry.frame.grid(row=5, column=1, sticky="ew") + self.side_gstate_label.grid(row=6, column=0, sticky="ew") + self.side_gstate_combo.frame.grid(row=6, column=1, sticky="ew") + + self.side_new_button = uiw.uiButton( + master=self.side_frame, command=self.new_side, text="Create Side" + ) + self.side_new_button.grid(row=7, column=0, columnspan=2, sticky="ew") + self.side_update_button = uiw.uiButton( + master=self.side_frame, + command=self.update_side, + text="Update Side" + ) + self.side_update_button.grid( + row=8, column=0, columnspan=2, sticky="ew" + ) + self.side_delete_button = uiw.uiCarefulButton( + master=self.side_frame, + command=self.delete_side, + text="Delete Side" + ) + self.side_delete_button.grid( + row=9, column=0, columnspan=2, sticky="ew" + ) + + # OBJ SELECTION + self.add_obj_button = uiw.uiButton( + master=self.add_remove_frame, + command=self.toggle_add_obj, + text="Add Object", + bg="red", + ) + self.add_obj_button.config(bg="red") + self.add_obj_button.grid(row=1, column=0, sticky="ew") + self.remove_obj_button = uiw.uiButton( + master=self.add_remove_frame, + command=self.toggle_remove_obj, + text="Remove Object", + ) + self.remove_obj_button.config(bg="red") + self.remove_obj_button.grid(row=2, column=0, sticky="ew") + + self.add_item_button = uiw.uiButton( + master=self.add_remove_frame, + command=self.toggle_add_item, + text="Add Item", + bg="red", + ) + self.add_item_button.config(bg="red") + self.add_item_button.grid(row=3, column=0, sticky="ew") + self.remove_item_button = uiw.uiButton( + master=self.add_remove_frame, + command=self.toggle_remove_item, + text="Remove Item", + ) + self.remove_item_button.config(bg="red") + self.remove_item_button.grid(row=4, column=0, sticky="ew") + + self.add_start_button = uiw.uiButton( + master=self.add_remove_frame, + command=self.toggle_add_start, + text="Add Start", + bg="red", + ) + self.add_start_button.config(bg="red") + self.add_start_button.grid(row=5, column=0, sticky="ew") + self.remove_start_button = uiw.uiButton( + master=self.add_remove_frame, + command=self.toggle_remove_start, + text="Remove Start", + ) + self.remove_start_button.config(bg="red") + self.remove_start_button.grid(row=6, column=0, sticky="ew") + + self.draw_label = uiw.uiLabel( + master=self.add_remove_frame, text="Toggle Add or Remove" + ) + self.draw_label.grid(row=0, column=0, sticky="ew") + self.draw_label.columnconfigure(0, weight=1) + self.obj_select_listbox_var = tk.StringVar() + self.obj_select_listbox = uiw.uiListBox( + master=self.obj_selection_frame, + listvariable=self.obj_select_listbox_var, + selectmode="single", + ) + self.obj_select_listbox.bind("<>", self.object_clicked) + self.obj_select_listbox.pack(side=tk.TOP, fill=tk.BOTH, expand=True) + + self.item_select_listbox_var = tk.StringVar() + self.item_select_listbox = uiw.uiListBox( + master=self.item_selection_frame, + listvariable=self.item_select_listbox_var, + selectmode="single", + ) + self.item_select_listbox.bind("<>", self.item_clicked) + self.item_select_listbox.pack(side=tk.TOP, fill=tk.BOTH, expand=True) + + # BUTTON ROW + self.map_create_button = uiw.uiButton( + master=self.button_row, command=self.create_map, text="New Map" + ) + self.map_create_button.pack(side=tk.LEFT) + self.map_update_button = uiw.uiButton( + master=self.button_row, command=self.update_map, text="Update Map" + ) + self.map_update_button.pack(side=tk.LEFT) + self.map_delete_button = uiw.uiCarefulButton( + master=self.button_row, command=self.delete_map, text="Delete Map" + ) + self.map_delete_button.pack(side=tk.LEFT) + self.home_button = uiw.uiButton( + master=self.button_row, command=self.goto_home, text="Home" + ) + self.home_button.pack(side=tk.RIGHT) + self.save_to_json_button = uiw.uiCarefulButton( + master=self.button_row, + command=self.save_to_json, + text="Save Map to JSON" + ) + self.save_to_json_button.pack(side=tk.RIGHT) + + def all_toggles_red(self): + self.add_obj_button.config(bg="red") + self.remove_obj_button.config(bg="red") + self.add_item_button.config(bg="red") + self.remove_item_button.config(bg="red") + self.add_start_button.config(bg="red") + self.remove_start_button.config(bg="red") + + def toggle_add_obj(self): + self.all_toggles_red() + if self.add_remove_var == AddRemove.AddObject: + self.add_remove_var = AddRemove.Info + else: + self.add_remove_var = AddRemove.AddObject + self.add_obj_button.config(bg="green") + + def toggle_remove_obj(self): + self.all_toggles_red() + if self.add_remove_var == AddRemove.DelObject: + self.add_remove_var = AddRemove.Info + else: + self.add_remove_var = AddRemove.DelObject + self.remove_obj_button.config(bg="green") + + def toggle_add_item(self): + self.all_toggles_red() + if self.add_remove_var == AddRemove.AddItem: + self.add_remove_var = AddRemove.Info + else: + self.add_remove_var = AddRemove.AddItem + self.add_item_button.config(bg="green") - for x in range(w): - for y in range(h): + def toggle_remove_item(self): + self.all_toggles_red() + if self.add_remove_var == AddRemove.DelItem: + self.add_remove_var = AddRemove.Info + else: + self.add_remove_var = AddRemove.DelItem + self.remove_item_button.config(bg="green") - if x != 0 and x != w - 1 and y != 0 and y != h - 1: + def toggle_add_start(self): + self.all_toggles_red() + if self.add_remove_var == AddRemove.AddStart: + self.add_remove_var = AddRemove.Info + else: + self.add_remove_var = AddRemove.AddStart + self.add_start_button.config(bg="green") - tile_id = self.canvas.draw_tile(x=x, y=y, fill="snow4") + def toggle_remove_start(self): + self.all_toggles_red() + if self.add_remove_var == AddRemove.DelStart: + self.add_remove_var = AddRemove.Info + else: + self.add_remove_var = AddRemove.DelStart + self.remove_start_button.config(bg="green") - self.canvas_background_tile_ids.append(tile_id) + def populate_map_listbox(self): - if (x == 0 and (y != 0 and y != h - 1)) or ( - x == w - 1 and (y != 0 and y != h - 1) - ): - RCnum_id = self.canvas.draw_rc_number( - x=x, y=y, fill="gray35", text=str(y - 1) + self.map_select_listbox.delete(0, tk.END) + map_data = self.ldr.get_map_templates() + entries = [] + for m in map_data.values(): + entry = f"{m['id']}:{m['name']}" + entries.append(entry) + self.map_select_listbox_var.set(entries) + + def populate_obj_listbox(self): + + self.obj_select_listbox.delete(0, tk.END) + obj_data = self.ldr.get_obj_templates() + entries = [] + for o in obj_data.values(): + entry = f"{o['id']}:{o['name']}" + entries.append(entry) + self.obj_select_listbox_var.set(entries) + + def populate_item_listbox(self): + + self.item_select_listbox.delete(0, tk.END) + item_data = self.ldr.get_item_templates() + entries = [] + for i in item_data.values(): + entry = f"{i['id']}:{i['name']}" + entries.append(entry) + self.item_select_listbox_var.set(entries) + + def object_clicked(self, event=None): + self.item_select_listbox.selection_clear(0, tk.END) + + def item_clicked(self, event=None): + self.obj_select_listbox.selection_clear(0, tk.END) + + def create_map(self): + map_data = self.ldr.get_map_templates() + good_id = False + while not good_id: + map_id = tk.simpledialog.askstring( + "New Map ID", + "Please enter a unique ID for the new map.\nThe map will " + + "be populated with default data. Use the 'Update Map' " + + "button to further customize the map.", + ) + if len(map_id) == 0: + tk.messagebox.showwarning( + "Error", + "You must enter a non-empty map ID." + ) + elif map_id in map_data.keys(): + tk.messagebox.showwarning( + "Error", "This ID already exists, please enter a new ID." + ) + else: + good_id = True + + new_map_data = { + "id": map_id, + "name": "NO NAME", + "width": 10, + "height": 10, + "edge_obj_id": "", + "desc": "", + "objects": [], + "items": [], + "sides": [], + } + + map_data.update({map_id: new_map_data}) + self.populate_map_listbox() + + def verify_new_map_size(self, cur_map, new_height, new_width): + for obj in cur_map["objects"]: + x = obj["x"] + y = obj["y"] + if x < 1 or y < 1 or x >= new_width or y >= new_height: + return False + for itm in cur_map["items"]: + x = itm["x"] + y = itm["y"] + if x < 1 or y < 1 or x >= new_width or y >= new_height: + return False + for side in cur_map["sides"].values(): + for start in side["starting_locations"]: + x = start["x"] + y = start["y"] + if x < 1 or y < 1 or x >= new_width or y >= new_height: + return False + + return True + + def update_map(self): + + cur_map = self.get_currently_selected_map() + if cur_map is not None: + map_data = self.ldr.get_map_templates() + map_id = self.map_id_entry.entry.get() + if map_id != cur_map["id"]: + if map_id in map_data.keys(): + tk.messagebox.showwarning( + "Duplicate Map ID", + "The new map ID already exists. Map IDs must be " + + "unique. No changes have been made to the map.", + ) + return + else: + old_id = cur_map["id"] + cur_map["id"] = map_id + del map_data[old_id] + map_data[map_id] = cur_map + map_name = self.map_name_entry.entry.get() + if map_name != cur_map["name"]: + if len(map_name) > 0: + cur_map["name"] = map_name + else: + tk.messagebox.showwarning( + "Empty Name", + "Map name cannot be empty." ) - self.canvas_background_RCnum_ids.append(RCnum_id) - elif (y == 0 and (x != 0 and x != w - 1)) or ( - y == h - 1 and (x != 0 and x != w - 1) - ): - RCnum_id = self.canvas.draw_rc_number( - x=x, y=y, fill="gray35", text=str(x - 1) + map_desc = self.map_desc_text.get(1.0, tk.END) + cur_map["desc"] = map_desc + map_width = self.map_width_entry.entry.get() + map_height = self.map_height_entry.entry.get() + try: + map_width = int(map_width) + map_height = int(map_height) + if map_width < 1 or map_height < 1: + raise Exception("New height or width is less than 1.") + else: + good = self.verify_new_map_size( + cur_map, map_width, map_height ) - self.canvas_background_RCnum_ids.append(RCnum_id) - - def add_object_draw_id(self, _uuid, _drawID): - """Adds object draw id to obj draw id list""" - self.obj_drawIDs[_uuid] = _drawID - - def get_object_draw_id(self, _uuid): - """Gets object draw id""" - try: - return self.obj_drawIDs[_uuid] - except KeyError: - self.logger.error( - "UIMapConfig: KeyError " + str(_uuid) + " in getObjectDrawID()." + if good: + cur_map["width"] = map_width + cur_map["height"] = map_height + else: + raise Exception( + "There are objects, items or starting locations " + + "outside the new map bounds. You will need " + + "to move or remove them before resizing the map." + ) + except Exception as e: + tk.messagebox.showwarning( + "Invalid Map Width or Height", + f"{e} {map_width} {map_height}" + ) + + map_edge_obj_id = self.map_edge_id_entry.entry.get() + edge_obj = self.ldr.get_obj_template(map_edge_obj_id) + if edge_obj is not None: + cur_map["edge_obj_id"] = map_edge_obj_id + else: + tk.messagebox.showwarning( + "Invalid Map Edge Object", + "The edge object ID must be a legitimate object ID.", + ) + + self.populate_map_listbox() + + for i, map_entry in enumerate( + self.map_select_listbox.get(0, tk.END) + ): + map_id, map_name = map_entry.split(":") + if map_id == cur_map["id"]: + self.map_select_listbox.selection_clear(0, tk.END) + self.map_select_listbox.selection_set(i) + self.map_select_listbox.activate(i) + self.show_map() + + def delete_map(self): + cur_map = self.get_currently_selected_map() + if cur_map is not None: + self.ldr.delete_map(cur_map["id"]) + self.populate_map_listbox() + + def goto_home(self): + self.controller.show_frame("home_page") + + def save_to_json(self): + self.ldr.save_map_templates() + + def new_side(self, event=None): + cur_map = self.get_currently_selected_map() + if cur_map is not None: + sides = cur_map["sides"] + side_id = tk.simpledialog.askstring( + "New Side ID", "Enter a new side ID. It must be unique." ) + if side_id is not None: + if len(side_id) == 0: + tk.messagebox.showwarning( + "Invalid Side ID", "A side ID cannot be empty." + ) + elif side_id in sides: + tk.messagebox.showwarning( + "Invalid Side ID", + "A side ID must be unique. There is already a " + + f"side with the ID {side_id}.", + ) + else: + new_side = { + "id": side_id, + "color": "#000000", + "name": side_id, + "num_agents": 0, + "random_placement": False, + "starting_locations": [], + "gstate": None, + } + sides[side_id] = new_side + self.show_map_info(cur_map) + self.select_side(side_id) + + def update_side(self, event=None): + side = self.get_currently_selected_side() + if side is not None: + cur_map = self.get_currently_selected_map() + # Name + side_name = self.side_name_entry.entry.get() + if len(side_name) == 0: + tk.messagebox.showwarning( + "Invalid Side Name", "A side name cannot be empty." + ) + else: + side["name"] = side_name + + # Num agents + num_agents = self.side_num_agents_entry.entry.get() + try: + num_agents = int(num_agents) + if num_agents < 0: + tk.messagebox.showwarning( + "Invalid Number of Agents", + "The number of agents cannot be negative.", + ) + else: + side["num_agents"] = num_agents + except Exception: + tk.messagebox.showwarning( + "Invalid Number of Agents", + "The value provided cannot be cast to an integer.", + ) + + # Color - There are no checks. + color = self.side_color_entry.entry.get() + side["color"] = color + + # Random placement + rand_placement = self.side_random_entry.entry.get() + side["random_placement"] = zfunctions.to_bool(rand_placement) + + # Goal State + goal_state = self.side_gstate_combo.combobox.get() + if len(goal_state) == 0: + tk.messagebox.showwarning( + "Invalid Goal State", + "A side must have a goal state. It cannot be empty.", + ) + else: + side["gstate"] = goal_state + + # ID + side_id = self.side_id_entry.entry.get() + if side_id != side["id"]: + + sides = cur_map["sides"] + if len(side_id) == 0: + tk.messagebox.showwarning( + "Invalid Side ID", "A side ID cannot be empty." + ) + elif side_id in sides: + tk.messagebox.showwarning( + "Invalid Side ID", + "A side ID must be unique. There is already a side " + + f"with the ID {side_id}.", + ) + else: + for k, v in sides.items(): + if v["id"] == side["id"]: + del sides[v["id"]] + break + side["id"] = side_id + sides[side_id] = side + + self.show_side_entries(cur_map) + self.select_side(side["id"]) + + def delete_side(self, event=None): + cur_side = self.get_currently_selected_side() + if cur_side is not None: + cur_map = self.get_currently_selected_map() + sides = cur_map["sides"] + for side_id, side in sides.items(): + if side["id"] == cur_side["id"]: + del sides[side_id] + break + self.show_side_entries(cur_map) + + def get_currently_selected_map(self): + map_index = self.map_select_listbox.curselection() + if len(map_index) == 1: + map_entry = self.map_select_listbox.get(map_index[0]) + map_id, map_name = map_entry.split(":") + return self.ldr.get_map_template(map_id) + else: return None - def init_objects(self): - """Draws initial object state + def get_currently_selected_side(self): + side_index = self.side_select_listbox.curselection() + if len(side_index) == 1: + side_entry = self.side_select_listbox.get(side_index[0]) + side_id, side_name = side_entry.split(":") + cur_map = self.get_currently_selected_map() + if cur_map is not None: + return cur_map["sides"][side_id] + else: + return None + else: + return None - Gets and adds draw data for every object to draw data list - Draws each object on canvas - """ - draw_data = [] - - for curr_obj in self.objs.values(): - draw_data.append(curr_obj.get_draw_data()) - - for dd in draw_data: - obj_id = self.canvas.draw_obj(dd=dd) - self.add_object_draw_id(dd["uuid"], obj_id) - - def add_item_draw_id(self, _uuid, _drawID): - """Adds item draw id to item draw id list""" - self.item_drawIDs[_uuid] = _drawID - - def get_item_draw_id(self, _uuid): - """Gets item draw id""" - try: - return self.item_drawIDs[_uuid] - except KeyError: - self.logger.error( - "UIMapConfig: KeyError " + str(_uuid) + " in getObjectDrawID()." - ) + def get_currently_selected_obj(self): + obj_index = self.obj_select_listbox.curselection() + if len(obj_index) == 1: + obj_entry = self.obj_select_listbox.get(obj_index[0]) + obj_id, obj_name = obj_entry.split(":") + return self.ldr.get_obj_template(obj_id) + else: + return None + + def get_currently_selected_item(self): + item_index = self.item_select_listbox.curselection() + if len(item_index) == 1: + item_entry = self.item_select_listbox.get(item_index[0]) + item_id, item_name = item_entry.split(":") + return self.ldr.get_item_template(item_id) + else: return None - def init_items(self): - """Draws initial item state + def select_side(self, _id): + + for i, side_entry in enumerate( + self.side_select_listbox.get(0, tk.END) + ): + side_id, side_name = side_entry.split(":") + if side_id == _id: + self.side_select_listbox.selection_clear(0, tk.END) + self.side_select_listbox.selection_set(i) + self.side_select_listbox.activate(i) + self.show_side() + + def clear_map_info(self): + self.map_id_entry.entry.delete(0, tk.END) + self.map_name_entry.entry.delete(0, tk.END) + self.map_desc_text.delete(1.0, tk.END) + self.map_width_entry.entry.delete(0, tk.END) + self.map_height_entry.entry.delete(0, tk.END) + self.map_edge_id_entry.entry.delete(0, tk.END) + self.side_select_listbox.delete(0, tk.END) + + def show_map_info(self, cur_map): + self.clear_map_info() + + self.map_id_entry.entry.insert(0, cur_map["id"]) + self.map_name_entry.entry.insert(0, cur_map["name"]) + self.map_desc_text.insert(1.0, cur_map["desc"]) + self.map_width_entry.entry.insert(0, cur_map["width"]) + self.map_height_entry.entry.insert(0, cur_map["height"]) + self.map_edge_id_entry.entry.insert(0, cur_map["edge_obj_id"]) + + self.show_side_entries(cur_map) + + def show_side_entries(self, cur_map): + self.clear_side() + # Add sides to the side listbox + side_entries = [] + for side in cur_map["sides"].values(): + entry = f"{side['id']}:{side['name']}" + side_entries.append(entry) + self.side_select_listbox_var.set(side_entries) + + def show_map(self, event=None): + + if self.canvas is not None: + self.canvas.destroy() + self.x_bar.destroy() + self.y_bar.destroy() + self.canvas = None + self.x_bar = None + self.y_bar = None + # self.cur_map = None - Gets and adds draw data for every item to draw data list - Draws each item on canvas + cur_map = self.get_currently_selected_map() + if cur_map is not None: + + self.show_map_info(cur_map) + + # Get map attributes + map_width = cur_map["width"] + map_height = cur_map["height"] + + self.char_offset = (self.cell_size - self.map_obj_char_size) / 2 + self.map_obj_font = tk.font.Font( + family="TkFixedFont", size=self.map_obj_char_size + ) + self.map_item_font = tk.font.Font( + family="TkFixedFont", size=self.map_item_char_size + ) + + self.x_bar = tk.Scrollbar(self.map_frame, orient=tk.HORIZONTAL) + self.y_bar = tk.Scrollbar(self.map_frame, orient=tk.VERTICAL) + self.canvas = uiw.uiCanvas( + master=self.map_frame, + width=800, + height=800, + xscrollcommand=self.x_bar.set, + yscrollcommand=self.y_bar.set, + scrollregion=( + 0, + 0, + (map_width + 2) * self.cell_size, + (map_height + 2) * self.cell_size, + ), + cell_size=self.cell_size, + border_size=self.border_size, + obj_char_size=self.map_obj_char_size, + item_char_size=self.map_item_char_size, + char_offset=self.char_offset, + obj_font=self.map_obj_font, + item_font=self.map_item_font, + ) + self.y_bar.configure(command=self.canvas.yview) + self.x_bar.configure(command=self.canvas.xview) + self.y_bar.pack(side=tk.RIGHT, fill=tk.Y) + self.x_bar.pack(side=tk.BOTTOM, fill=tk.X) + self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + self.canvas.yview_moveto(0.0) + self.canvas.xview_moveto(0.0) + self.canvas.bind("", self.map_mouse_click) + + self.draw_tiles(map_width, map_height) + self.draw_rc_labels(map_width, map_height) + self.draw_objects(cur_map) + self.draw_edge_objs(cur_map) + self.draw_starting_locations(cur_map) + self.draw_items(cur_map) + + def clear_side(self): + self.side_id_entry.entry.delete(0, tk.END) + self.side_name_entry.entry.delete(0, tk.END) + self.side_num_agents_entry.entry.delete(0, tk.END) + self.side_color_entry.entry.delete(0, tk.END) + self.side_random_entry.entry.delete(0, tk.END) + + def show_side(self, event=None): + self.clear_side() + cur_side = self.get_currently_selected_side() + if cur_side is not None: + self.side_id_entry.entry.insert(0, cur_side["id"]) + self.side_name_entry.entry.insert(0, cur_side["name"]) + self.side_num_agents_entry.entry.insert(0, cur_side["num_agents"]) + self.side_color_entry.entry.insert(0, cur_side["color"]) + self.side_random_entry.entry.insert( + 0, cur_side["random_placement"] + ) + + for i, gs in enumerate(self.side_gstate_combo.combobox["values"]): + if cur_side["gstate"] == gs: + self.side_gstate_combo.combobox.current(i) + + def map_mouse_click(self, event): """ - draw_data = [] + Handles a mouse click on the map canvas. + + What happens on a click depends on which (if any) of the add/remove + toggle buttons are green (on). + + There are certain restrictions. + + Only 1 object can be in a cell. + + Only 1 starting location can be in + a cell. A cell cannot contain an object AND starting location because + starting locations will be filled (potentially) with agent objects when + the sim starts. + + Items and objects can occupy the same location. + + There is no limit on the number of items in a cell; however, each item + can only be on the map once. + """ + cur_map = self.get_currently_selected_map() + if cur_map is None: + return + + x, y = self.canvas.mousexy_to_cellxy(event.x, event.y) + if x >= 0 and y >= 0 \ + and x < cur_map["width"] and y < cur_map["height"]: + + # Find out what is in the cell, if anything. + things = self.whats_in_cell_xy(cur_map, x, y) + + # Object + if ( + self.add_remove_var == AddRemove.AddObject + or self.add_remove_var == AddRemove.DelObject + ): + self.handle_add_remove_object(x, y, things, cur_map) + + # Item + elif ( + self.add_remove_var == AddRemove.AddItem + or self.add_remove_var == AddRemove.DelItem + ): + self.handle_add_remove_item(x, y, things, cur_map) + + # Starting location + elif ( + self.add_remove_var == AddRemove.AddStart + or self.add_remove_var == AddRemove.DelStart + ): + self.handle_add_remove_start(x, y, things, cur_map) + + def whats_in_cell_xy(self, cur_map, x, y): + things = { + "object_index": None, + "item_indexes": [], + "start_index": None + } + for i in range(len(cur_map["objects"])): + obj = cur_map["objects"][i] + if obj["x"] == x and obj["y"] == y: + things["object_index"] = i + break + + for i in range(len(cur_map["items"])): + itm = cur_map["items"][i] + if itm["x"] == x and itm["y"] == y: + things["item_indexes"].append(i) + + side = self.get_currently_selected_side() + if side is not None: + for i in range(len(side["starting_locations"])): + sl = side["starting_locations"][i] + if sl["x"] == x and sl["y"] == y: + things["start_index"] = i + break + + return things + + def handle_add_remove_object(self, x, y, things, cur_map): + if self.add_remove_var == AddRemove.AddObject: + # If the cell has no obj or start loc already... + if things["object_index"] is None \ + and things["start_index"] is None: + obj = self.get_currently_selected_obj() + if obj is not None: + obj_entry = {"id": obj["id"], "x": x, "y": y} + cur_map["objects"].append(obj_entry) + self.canvas.draw_sprite( + x=x, + y=y, + sprite_filename=obj["alive_sprite_filename"], + sprite_type="object", + ) + elif self.add_remove_var == AddRemove.DelObject: + if things["object_index"] is not None: + del cur_map["objects"][things["object_index"]] + self.canvas.remove_obj_at(x, y) + + def handle_add_remove_item(self, x, y, things, cur_map): + if self.add_remove_var == AddRemove.AddItem: + itm = self.get_currently_selected_item() + if itm is not None: + + for on_map_item in cur_map["items"]: + if on_map_item["id"] == itm["id"]: + tk.messagebox.showwarning( + "Error", + "A map cannot have more than one copy of an item." + ) + return + + item_entry = {"id": itm["id"], "x": x, "y": y} + cur_map["items"].append(item_entry) + self.canvas.draw_sprite( + x=x, + y=y, + sprite_filename=itm["sprite_filename"], + id=itm["id"], + sprite_type="item", + ) + elif self.add_remove_var == AddRemove.DelItem: + if len(things["item_indexes"]) == 1: + _id = cur_map["items"][things["item_indexes"][0]]["id"] + del cur_map["items"][things["item_indexes"][0]] + self.canvas.remove_item_at(x, y, _id) + elif len(things["item_indexes"]) > 1: + + msg = f"The items present in cell {x},{y} are:\n" + for index in things["item_indexes"]: + itm_map_entry = cur_map["items"][index] + itm = self.ldr.get_item_template(itm_map_entry["id"]) + msg += f"[{index}] {itm['id']} {itm['name']}\n" + msg += "\nGive number in the square brackets of the item " + + "you wish to remove from the cell." + + index = tk.simpledialog.askinteger("Remove Item", msg) + + if index is not None and index in things["item_indexes"]: + _id = cur_map["items"][index]["id"] + del cur_map["items"][index] + self.canvas.remove_item_at(x, y, _id) + else: + tk.messagebox.showwarning( + "Error", "Invalid index entered." + ) + else: + print("Nothing to delete") + + def handle_add_remove_start(self, x, y, things, cur_map): + + if self.add_remove_var == AddRemove.AddStart: + side = self.get_currently_selected_side() + # If a side is selected, and... + if side is not None: + # There isn't already a start or obj in this cell... + if things["start_index"] is None and \ + things["object_index"] is None: + facing = tk.simpledialog.askinteger( + "Enter Facing", + "Which direction (in degrees) will the agent face " + + "when the match starts [0, 360]?", + ) + + if facing < 0 or facing > 360: + tk.messagebox.showwarning( + "Error", "Facing must be 0 to 360 degrees." + ) + else: + start = {"x": x, "y": y, "facing": 0} + index = len(side["starting_locations"]) + side["starting_locations"].append(start) + self.canvas.draw_starting_location( + x, y, index, side["color"] + ) + + elif self.add_remove_var == AddRemove.DelStart: + side = self.get_currently_selected_side() + + if side is not None: + if things["start_index"] is not None: + for i, sl in enumerate(side["starting_locations"]): + if sl["x"] == x and sl["y"] == y: + del side["starting_locations"][i] + self.canvas.remove_all_starting_locations() + self.draw_starting_locations(cur_map) + break + else: + tk.messagebox.showwarning( + "Error", + "You must select a side to remove a starting location." + ) + + def draw_tiles(self, width, height): + """Draws tiles on canvas""" + + for x in range(width): + for y in range(height): + + self.canvas.draw_tile(x=x, y=y, fill="#DDDDDD") + + def draw_rc_labels(self, width, height): + """Draws tiles on canvas""" + + self.canvas.draw_row_labels( + width=width, height=height, fill="#000000" + ) + self.canvas.draw_column_labels( + width=width, height=height, fill="#000000" + ) + + def draw_objects(self, cur_map): + """Draws placed object on the map""" + + for obj_entry in cur_map["objects"]: + + obj = self.ldr.get_obj_template(obj_entry["id"]) + + sprite_filename = obj["alive_sprite_filename"] + + self.canvas.draw_sprite( + x=obj_entry["x"], + y=obj_entry["y"], + sprite_filename=sprite_filename, + sprite_type="object", + ) + + def draw_edge_objs(self, cur_map): + """ + Draws the edge objects + """ + edge_obj_id = cur_map["edge_obj_id"] + edge_obj = self.ldr.get_obj_template(edge_obj_id) + if edge_obj is not None: + map_width = cur_map["width"] + map_height = cur_map["height"] + sprite_filename = edge_obj["alive_sprite_filename"] + + # Draw top and bottom edges + for x in range(map_width): + self.canvas.draw_sprite( + x=x, + y=0, + sprite_filename=sprite_filename, + sprite_type="object" + ) + self.canvas.draw_sprite( + x=x, + y=map_height - 1, + sprite_filename=sprite_filename, + sprite_type="object", + ) + + # Draw left and right (minus the corner hexes) + for y in range(1, map_height - 1): + self.canvas.draw_sprite( + x=0, + y=y, + sprite_filename=sprite_filename, + sprite_type="object" + ) + self.canvas.draw_sprite( + x=map_width - 1, + y=y, + sprite_filename=sprite_filename, + sprite_type="object", + ) + else: + tk.messagebox.showwarning( + "Warning", + "This map does not have a valid edge object.\n\nEdge objects " + + "define the edge of the map and prevent agents from moving " + + "outside its bounds.\n\nTechnically, any object can be " + + "used to define a map edge,\nbut it should be " + + "indestructible (i.e. have a lot of health).", + ) + + def draw_items(self, cur_map): + """Draws items on the map""" + + for item_entry in cur_map["items"]: + + obj = self.ldr.get_item_template(item_entry["id"]) + + sprite_filename = obj["sprite_filename"] + + self.canvas.draw_sprite( + x=item_entry["x"], + y=item_entry["y"], + id=obj["id"], + sprite_filename=sprite_filename, + sprite_type="item", + ) + + def draw_starting_locations(self, cur_map): + """Adds ai-controlled object to obj list""" + # Add possible team and ai-controlled obj locations + sides = cur_map["sides"] - for curr_items in self.items.values(): - draw_data.append(curr_items.get_draw_data()) + for side in sides.values(): + color = side["color"] + starting_locations = side["starting_locations"] - for dd in draw_data: - item_id = self.canvas.draw_obj(dd=dd) - self.add_item_draw_id(dd["uuid"], item_id) + for i in range(len(starting_locations)): + loc = starting_locations[i] + x = loc["x"] + y = loc["y"] + self.canvas.draw_starting_location(x, y, i, color) diff --git a/ui_object_config.py b/ui_object_config.py new file mode 100644 index 0000000..7b7d0dc --- /dev/null +++ b/ui_object_config.py @@ -0,0 +1,528 @@ +import tkinter as tk +import zfunctions +from tkinter.messagebox import showwarning +from tkinter.simpledialog import askstring +import ui_widgets as uiw + + +class UIObjectConfig(tk.Frame): + def __init__(self, controller, ldr, master=None, logger=None): + super().__init__(master) + self.controller = controller + self.master = master + self.configure(bg=uiw.BGCOLOR) + self.logger = logger + self.ldr = ldr + self.build_ui() + + def validate_number_entry(self, input): + """ + Validates each entered value (input) to ensure it is a number. + """ + input.replace(".", "", 1) + input.replace("-", "", 1) + if input.isdigit() or input == "" or "-" in input or "." in input: + return True + + else: + return False + + def get_focused_entry(self): + """ + Returns the currently focused entry in advanced config. + """ + focused_entry = self.focus_get() + return focused_entry + + def build_ui(self): + """ + Initializes all widgets and places them. + """ + # Make frames + + self.main_frame = uiw.uiQuietFrame(master=self) + self.obj_selection_frame = uiw.uiQuietFrame(master=self.main_frame) + self.obj_info_frame = uiw.uiQuietFrame(master=self.main_frame) + self.obj_comp_frame = uiw.uiQuietFrame(master=self.main_frame) + self.comp_selection_frame = uiw.uiQuietFrame(master=self.main_frame) + self.button_row = uiw.uiQuietFrame(master=self.main_frame) + self.title_label = uiw.uiLabel( + master=self.main_frame, text="Object Configuration" + ) + self.validate_num = self.main_frame.register( + self.validate_number_entry + ) + + # Place frames + self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + self.title_label.pack(side=tk.TOP, fill="x") + self.button_row.pack(side=tk.BOTTOM, fill="x") + self.obj_selection_frame.pack(side=tk.LEFT, fill="y") + self.obj_info_frame.pack(side=tk.LEFT, fill="y") + self.obj_info_frame.columnconfigure(0, weight=1) + self.obj_info_frame.columnconfigure(1, weight=1) + self.obj_comp_frame.pack(side=tk.LEFT, fill="y") + self.obj_comp_frame.rowconfigure(1, weight=1) + # self.obj_info_frame.rowconfigure(11,weight=1) + self.comp_selection_frame.pack(side=tk.LEFT, fill="y") + self.comp_selection_frame.rowconfigure(1, weight=1) + + # Make widgets + self.select_obj_listbox_var = tk.StringVar() + self.select_obj_listbox = uiw.uiListBox( + master=self.obj_selection_frame, + listvariable=self.select_obj_listbox_var, + selectmode="single", + ) + self.select_obj_listbox.pack(side=tk.LEFT, fill=tk.BOTH) + self.select_obj_listbox.bind("<>", self.show_object) + + # Make Object Widgets + self.obj_id_label = uiw.uiLabel(master=self.obj_info_frame, text="ID") + self.obj_id_entry = uiw.EntryHelp( + master=self.obj_info_frame, text="An object's unique ID." + ) + self.obj_name_label = uiw.uiLabel( + master=self.obj_info_frame, text="Name" + ) + self.obj_name_entry = uiw.EntryHelp( + master=self.obj_info_frame, + text="An object's name. Names do not have to be unique.", + ) + self.obj_health_label = uiw.uiLabel( + master=self.obj_info_frame, text="Health" + ) + self.obj_health_entry = uiw.EntryHelp( + master=self.obj_info_frame, + text="An integer value giving the maximum/starting health of " + + "the object." + ) + self.obj_density_label = uiw.uiLabel( + master=self.obj_info_frame, text="Density" + ) + self.obj_density_entry = uiw.EntryHelp( + master=self.obj_info_frame, + text="An object's density determines how well radar " + + "scans penetrate and pass through the object. " + + "A high density object will stop radar scans of lower or " + + "equal value, preventing the scan from detecting objects " + + "on the other side.", + ) + self.obj_points_label = uiw.uiLabel( + master=self.obj_info_frame, text="Points" + ) + self.obj_points_entry = uiw.EntryHelp( + master=self.obj_info_frame, + text="Point value associated with the destruction of the object.", + ) + self.obj_char_label = uiw.uiLabel( + master=self.obj_info_frame, text="Display Char" + ) + self.obj_char_entry = uiw.EntryHelp( + master=self.obj_info_frame, + text="A single character used to display the object " + + "on the map when a sprite is not available.", + ) + self.obj_char_color_alive_label = uiw.uiLabel( + master=self.obj_info_frame, text="Alive Color" + ) + self.obj_char_color_alive_entry = uiw.EntryHelp( + master=self.obj_info_frame, + text="The color used to display the object's character " + + "when the object is alive.", + ) + self.obj_char_color_dead_label = uiw.uiLabel( + master=self.obj_info_frame, text="Dead Color" + ) + self.obj_char_color_dead_entry = uiw.EntryHelp( + master=self.obj_info_frame, + text="The color used to display the object's character " + + "when the object is dead.", + ) + self.obj_sprite_alive_name_label = uiw.uiLabel( + master=self.obj_info_frame, text="Alive Sprite" + ) + self.obj_sprite_alive_name_entry = uiw.EntryHelp( + master=self.obj_info_frame, + text="The filename of the sprite used to display " + + "the object when it is alive.", + ) + self.obj_sprite_dead_name_label = uiw.uiLabel( + master=self.obj_info_frame, text="Dead Sprite" + ) + self.obj_sprite_dead_name_entry = uiw.EntryHelp( + master=self.obj_info_frame, + text="The filename of the sprite used to display " + + "the object when it is dead.", + ) + + # Place obj widgets + self.obj_id_label.grid(row=0, column=0, sticky="ew") + self.obj_id_entry.frame.grid(row=0, column=1, sticky="ew") + self.obj_name_label.grid(row=1, column=0, sticky="ew") + self.obj_name_entry.frame.grid(row=1, column=1, sticky="ew") + self.obj_health_label.grid(row=2, column=0, sticky="ew") + self.obj_health_entry.frame.grid(row=2, column=1, sticky="ew") + self.obj_density_label.grid(row=3, column=0, sticky="ew") + self.obj_density_entry.frame.grid(row=3, column=1, sticky="ew") + self.obj_points_label.grid(row=4, column=0, sticky="ew") + self.obj_points_entry.frame.grid(row=4, column=1, sticky="ew") + self.obj_char_label.grid(row=5, column=0, sticky="ew") + self.obj_char_entry.frame.grid(row=5, column=1, sticky="ew") + self.obj_char_color_alive_label.grid(row=6, column=0, sticky="ew") + self.obj_char_color_alive_entry.frame.grid( + row=6, column=1, sticky="ew" + ) + self.obj_char_color_dead_label.grid(row=7, column=0, sticky="ew") + self.obj_char_color_dead_entry.frame.grid(row=7, column=1, sticky="ew") + self.obj_sprite_alive_name_label.grid(row=8, column=0, sticky="ew") + self.obj_sprite_alive_name_entry.frame.grid( + row=8, column=1, sticky="ew" + ) + self.obj_sprite_dead_name_label.grid(row=9, column=0, sticky="ew") + self.obj_sprite_dead_name_entry.frame.grid( + row=9, column=1, sticky="ew" + ) + + self.obj_comp_remove_label = uiw.uiLabel( + master=self.obj_comp_frame, text="Object's Components" + ) + self.obj_comp_remove_listbox_var = tk.StringVar() + self.obj_comp_remove_listbox = uiw.uiListBox( + master=self.obj_comp_frame, + listvariable=self.obj_comp_remove_listbox_var, + selectmode="multiple", + width=32, + ) + self.obj_comp_remove_button = uiw.uiButton( + master=self.obj_comp_frame, + command=self.remove_comps, + text="Remove Component", + ) + self.obj_comp_remove_label.grid( + row=0, column=0, columnspan=2, sticky="ew" + ) + self.obj_comp_remove_listbox.grid( + row=1, column=0, columnspan=2, sticky="nsew" + ) + self.obj_comp_remove_button.grid( + row=2, column=0, columnspan=2, sticky="ew" + ) + + self.obj_comp_add_label = uiw.uiLabel( + master=self.comp_selection_frame, text="All Components" + ) + self.obj_comp_add_listbox_var = tk.StringVar() + self.obj_comp_add_listbox = uiw.uiListBox( + master=self.comp_selection_frame, + listvariable=self.obj_comp_add_listbox_var, + selectmode="multiple", + width=32, + ) + self.obj_comp_add_button = uiw.uiButton( + master=self.comp_selection_frame, + command=self.add_comps, + text="Add Component(s)", + ) + self.obj_comp_add_label.grid( + row=0, column=0, columnspan=2, sticky="ew" + ) + self.obj_comp_add_listbox.grid( + row=1, column=0, columnspan=2, sticky="nsew" + ) + self.obj_comp_add_button.grid( + row=2, column=0, columnspan=2, sticky="ew" + ) + + # Main Buttons + self.obj_create_button = uiw.uiButton( + master=self.button_row, command=self.create_obj, text="New Object" + ) + self.obj_create_button.pack(side=tk.LEFT) + self.obj_update_button = uiw.uiButton( + master=self.button_row, + command=self.update_obj, + text="Update Object" + ) + self.obj_update_button.pack(side=tk.LEFT) + self.obj_delete_button = uiw.uiCarefulButton( + master=self.button_row, + command=self.delete_obj, + text="Delete Object" + ) + self.obj_delete_button.pack(side=tk.LEFT) + + self.home_button = uiw.uiButton( + master=self.button_row, + command=self.goto_home, + text="Home" + ) + self.home_button.pack(side=tk.RIGHT) + self.save_to_json_button = uiw.uiCarefulButton( + master=self.button_row, + command=self.save_to_json, + text="Save Objs to JSON" + ) + self.save_to_json_button.pack(side=tk.RIGHT) + + self.populate_obj_listbox() + + def populate_obj_listbox(self): + + self.clear_all_fields() + obj_data = self.ldr.get_obj_templates() + entries = [] + for _obj in obj_data.values(): + entry = f"{_obj['id']}:{_obj['name']}" + entries.append(entry) + entries = sorted(entries) + self.select_obj_listbox.delete(0, tk.END) + self.select_obj_listbox_var.set(entries) + + def clear_all_fields(self): + "Clears all the object info fields." + self.obj_id_entry.entry.delete(0, tk.END) + self.obj_name_entry.entry.delete(0, tk.END) + self.obj_health_entry.entry.delete(0, tk.END) + self.obj_density_entry.entry.delete(0, tk.END) + self.obj_points_entry.entry.delete(0, tk.END) + self.obj_char_entry.entry.delete(0, tk.END) + self.obj_char_color_alive_entry.entry.delete(0, tk.END) + self.obj_char_color_dead_entry.entry.delete(0, tk.END) + self.obj_sprite_alive_name_entry.entry.delete(0, tk.END) + self.obj_sprite_dead_name_entry.entry.delete(0, tk.END) + self.obj_comp_remove_listbox.delete(0, tk.END) + self.obj_comp_add_listbox.delete(0, tk.END) + + def select_obj_with_id(self, _id): + for index, entry in enumerate(self.select_obj_listbox.get(0, tk.END)): + obj_id, obj_name = entry.split(":") + if _id == obj_id: + self.select_obj_listbox.selection_clear(0, tk.END) + self.select_obj_listbox.selection_set(index) + self.select_obj_listbox.activate(index) + self.show_object() + break + + def show_object(self, event=None): + selected_obj = self.get_currently_selected_obj() + if selected_obj is not None: + self.clear_all_fields() + self.obj_id_entry.entry.insert(0, selected_obj["id"]) + self.obj_name_entry.entry.insert(0, selected_obj["name"]) + self.obj_health_entry.entry.insert(0, selected_obj["health"]) + self.obj_density_entry.entry.insert(0, selected_obj["density"]) + self.obj_points_entry.entry.insert(0, selected_obj["points"]) + self.obj_char_entry.entry.insert(0, selected_obj["character"]) + self.obj_char_color_alive_entry.entry.insert( + 0, selected_obj["color_alive"] + ) + self.obj_char_color_dead_entry.entry.insert( + 0, selected_obj["color_dead"] + ) + self.obj_sprite_alive_name_entry.entry.insert( + 0, selected_obj["alive_sprite_filename"] + ) + self.obj_sprite_dead_name_entry.entry.insert( + 0, selected_obj["dead_sprite_filename"] + ) + self.show_obj_comps(selected_obj) + self.show_all_comps() + + def show_obj_comps(self, _obj): + comp_data = self.ldr.get_comp_templates() + comp_entries = [] + for cid in _obj["comp_ids"]: + comp = comp_data[cid] + entry = f"{comp['id']}:{comp['name']}" + comp_entries.append(entry) + comp_entries = sorted(comp_entries) + self.obj_comp_remove_listbox_var.set(comp_entries) + + def show_all_comps(self): + comp_data = self.ldr.get_comp_templates() + entries = [] + for comp in comp_data.values(): + entry = f"{comp['id']}:{comp['name']}" + entries.append(entry) + entries = sorted(entries) + self.obj_comp_add_listbox_var.set(entries) + + def create_obj(self): + + obj_data = self.ldr.get_obj_templates() + good = False + while not good: + obj_id = askstring( + "Unique Object ID", + "Please enter an unique ID for the new object." + ) + if len(obj_id) == 0: + tk.messagebox.showwarning( + "Warning", + "You must enter a non-empty object ID to continue." + ) + elif obj_id in obj_data.keys(): + tk.messagebox.showwarning( + "Warning", + f"Object ID {obj_id} already exists," + + "please enter a new ID.", + ) + else: + good = True + + new_obj = { + "id": obj_id, + "name": "None", + "color_alive": "green", + "color_dead": "red", + "character": "O", + "health": 0, + "density": 0, + "comp_ids": [], + "points": 0, + "alive_sprite_filename": "", + "dead_sprite_filename": "", + } + obj_data.update({obj_id: new_obj}) + + # Repopulate the listbox of objects + self.populate_obj_listbox() + + # Select the new object so it can be displayed for the user. + self.select_obj_with_id(obj_id) + + def update_obj(self): + + obj_data = self.ldr.get_obj_templates() + current_obj = self.get_currently_selected_obj() + + new_id = self.obj_id_entry.entry.get() + new_name = self.obj_name_entry.entry.get() + new_health = self.obj_health_entry.entry.get() + new_density = self.obj_density_entry.entry.get() + new_points = self.obj_points_entry.entry.get() + new_char = self.obj_char_entry.entry.get() + new_alive_color = self.obj_char_color_alive_entry.entry.get() + new_dead_color = self.obj_char_color_dead_entry.entry.get() + new_alive_sprite = self.obj_sprite_alive_name_entry.entry.get() + new_dead_sprite = self.obj_sprite_dead_name_entry.entry.get() + + if new_id != current_obj["id"]: + if new_id in obj_data.keys(): + showwarning( + title="Warning", + message=f"ID {new_id} is in use by another object." + + "Please use another ID.", + ) + else: + old_id = current_obj["id"] + current_obj["id"] = new_id + del obj_data[old_id] + obj_data[new_id] = current_obj + + warnings = "" + + if len(new_name) > 0: + current_obj["name"] = new_name + else: + warnings += "WARNING: Name field cannot be empty.\n" + + if len(new_health) > 0 and zfunctions.is_int(new_health): + current_obj["health"] = new_health + else: + warnings += "WARNING: Health field must be an integer.\n" + + if len(new_density) > 0 and zfunctions.is_int(new_density): + current_obj["density"] = new_density + else: + warnings += "WARNING: Density field must be an integer.\n" + + if len(new_points) > 0 and zfunctions.is_int(new_points): + current_obj["points"] = new_points + else: + warnings += "WARNING: Points field must be an integer.\n" + + if len(new_char) == 1: + current_obj["character"] = new_char + else: + warnings += "WARNING: Character must be a single character.\n" + + # TODO: Some color and filename validation. Maybe. + current_obj["color_alive"] = new_alive_color + current_obj["color_dead"] = new_dead_color + current_obj["alive_sprite_filename"] = new_alive_sprite + current_obj["dead_sprite_filename"] = new_dead_sprite + + if len(warnings) > 0: + showwarning(title="Warning", message=warnings) + + self.populate_obj_listbox() + + self.select_obj_with_id(new_id) + self.show_object() + + def delete_obj(self): + """ + Deleted the selected object from the Loader's object templates + dictionary. + """ + obj_data = self.ldr.get_obj_templates() + current_obj = self.get_currently_selected_obj() + if current_obj is not None: + del obj_data[current_obj["id"]] + self.populate_obj_listbox() + + def add_comps(self): + current_obj = self.get_currently_selected_obj() + comps_to_add = self.get_comps_to_add() + for comp_id in comps_to_add: + current_obj["comp_ids"].append(comp_id) + self.show_object() + # self.obj_comp_add_listbox.selection_clear(0,tk.END) + + def remove_comps(self): + current_obj = self.get_currently_selected_obj() + comps_to_remove = self.get_comps_to_remove() + for comp_id in comps_to_remove: + current_obj["comp_ids"].remove(comp_id) + self.show_object() + + def goto_home(self): + self.controller.show_frame("home_page") + + def save_to_json(self): + self.ldr.save_obj_templates() + + def get_currently_selected_obj(self): + obj_index = self.select_obj_listbox.curselection() + if len(obj_index) == 1: + obj_entry = self.select_obj_listbox.get(obj_index[0]) + obj_id, obj_name = obj_entry.split(":") + return self.ldr.get_obj_template(obj_id) + else: + return None + + def get_comps_to_add(self): + comp_indexes = self.obj_comp_add_listbox.curselection() + comp_ids_to_add = [] + if len(comp_indexes) > 0: + for index in comp_indexes: + comp_entry = self.obj_comp_add_listbox.get(index) + comp_id, comp_name = comp_entry.split(":") + comp_ids_to_add.append(comp_id) + return comp_ids_to_add + else: + return [] + + def get_comps_to_remove(self): + comp_indexes = self.obj_comp_remove_listbox.curselection() + comp_ids_to_remove = [] + if len(comp_indexes) > 0: + for index in comp_indexes: + comp_entry = self.obj_comp_remove_listbox.get(index) + comp_id, comp_name = comp_entry.split(":") + comp_ids_to_remove.append(comp_id) + return comp_ids_to_remove + else: + return [] diff --git a/ui_scoreboard.py b/ui_scoreboard.py index 90bad6c..f30de0d 100644 --- a/ui_scoreboard.py +++ b/ui_scoreboard.py @@ -1,9 +1,16 @@ import tkinter as tk -from ui_widgets import * +import ui_widgets as uiw class ScoreboardFrame(tk.Frame): - def __init__(self, teams_scores_dict, controller, ui_sim, sim, master=None): + def __init__( + self, + teams_scores_dict, + controller, + ui_sim, + sim, + master=None + ): """Sets window and frame information and calls function to build UI""" super().__init__(master) @@ -38,7 +45,8 @@ def build_ui(self): """ # This converts the dictionary to tuples teams_scores = [ - (team, details["total"]) for team, details in self.teams_scores_dict.items() + (team, details["total"]) + for team, details in self.teams_scores_dict.items() ] # Sort tuples in descending order @@ -47,20 +55,30 @@ def build_ui(self): ) # Title - title_label = uiLabel(master=self, text="Final Scores", font=("Arial", 16)) + title_label = uiw.uiLabel( + master=self, text="Final Scores", font=("Arial", 16) + ) title_label.config(highlightthickness=0) title_label.grid(row=0, column=0, columnspan=2, pady=(0, 10)) # Scores - for index, (team, score) in enumerate(self.teams_scores_sorted, start=1): - team_label = uiLabel(master=self, text=f"{team}", font=("Arial", 14)) + for index, (team, score) in enumerate( + self.teams_scores_sorted, start=1 + ): + team_label = uiw.uiLabel( + master=self, text=f"{team}", font=("Arial", 14) + ) team_label.config(highlightthickness=0) - score_label = uiLabel(master=self, text=f"{score}", font=("Arial", 14)) + score_label = uiw.uiLabel( + master=self, text=f"{score}", font=("Arial", 14) + ) score_label.config(highlightthickness=0) team_label.grid(row=index, column=0, columnspan=2, sticky="w") score_label.grid(row=index, column=1, columnspan=2, sticky="e") - home_button = uiButton(master=self, command=self.home_page, text="Home") + home_button = uiw.uiButton( + master=self, command=self.home_page, text="Home" + ) home_button.grid(row=index + 1, column=0) def home_page(self): diff --git a/ui_setup.py b/ui_setup.py index 80cd27e..b2c89f2 100644 --- a/ui_setup.py +++ b/ui_setup.py @@ -1,20 +1,11 @@ import tkinter as tk -from tkinter import messagebox -from tkinter.font import Font -import tkinter.scrolledtext as scrolltext -import importlib.util -import sys -import os import queue -import logging - import sim import loader import ui_sim -import ui_advanced_config import msgs -from zexceptions import * -from ui_widgets import * +import zexceptions +import ui_widgets as uiw class UISetup(tk.Frame): @@ -48,7 +39,9 @@ def update_team_names(self): self.teams_list.insert(tk.END, name) for k, v in self.sim.get_sides().items(): self.sides_list.insert(tk.END, k) - self.team_assignments_list.insert(tk.END, str(self.sim.get_team_name(k))) + self.team_assignments_list.insert( + tk.END, str(self.sim.get_team_name(k)) + ) def update_map_names(self): """Updates map names""" @@ -68,16 +61,18 @@ def select_map(self, event): # Locate the map, copy and give to sim map_ids = self.ldr.get_map_ids() - selected_map = self.ldr.copy_map_template(map_ids[cur_selection[0]]) + selected_map = self.ldr.copy_map_template( + map_ids[cur_selection[0]] + ) self.sim.set_map(selected_map) # Construct the map info string - map_info = "NAME: " + selected_map.get_data("name") + "\n" - map_info += "DESC: " + selected_map.get_data("desc") + "\n" - map_info += "WIDTH: " + str(selected_map.get_data("width")) + "\n" - map_info += "HEIGHT: " + str(selected_map.get_data("height")) + "\n" - map_info += "TEAMS: " + str(selected_map.get_data("teams")) + "\n" - map_info += "AGENTS: " + str(selected_map.get_data("agents")) + "\n" + map_info = f"NAME: {selected_map.get_data('name')}\n" + map_info += f"DESC: {selected_map.get_data('desc')}\n" + map_info += f"WIDTH: {selected_map.get_data('width')}\n" + map_info += f"HEIGHT: {selected_map.get_data('height')}\n" + map_info += f"TEAMS: {selected_map.get_data('teams')}\n" + map_info += f"AGENTS: {selected_map.get_data('agents')}\n" map_info += "RANDOMLY PLACED OBJECTS:\n" if "rand_objects" in selected_map.data: for k, v in selected_map.data["rand_objects"].items(): @@ -87,7 +82,7 @@ def select_map(self, event): map_info += "HAND-PLACED OBJECTS\n" if "placed_objects" in selected_map.data: for k, v in selected_map.data["placed_objects"].items(): - map_info += " ID:" + str(k) + " AMT:" + str(len(v)) + "\n" + map_info += f" ID:{k} AMT:{len(v)}\n" else: map_info += " NONE\n" @@ -103,50 +98,58 @@ def build_ui(self): Places map select, places team/side assignments, places adv config button, places start button """ - ## PAGE TITLE + # PAGE TITLE - self.title_label = uiLabel(master=self, text="CONFIGURATION") + self.title_label = uiw.uiLabel(master=self, text="CONFIGURATION") self.title_label.pack(side=tk.TOP) ####################################################################### - ## SIM UI + # SIM UI ####################################################################### - self.sim_frame = uiQuietFrame(master=self) - self.sim_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True, padx=10, pady=10) + self.sim_frame = uiw.uiQuietFrame(master=self) + self.sim_frame.pack( + side=tk.BOTTOM, fill=tk.BOTH, expand=True, padx=10, pady=10 + ) - self.start_button_frame = uiQuietFrame(master=self.sim_frame) + self.start_button_frame = uiw.uiQuietFrame(master=self.sim_frame) self.start_button_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) - self.adv_config_button_frame = uiQuietFrame(master=self.sim_frame) - self.adv_config_button_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + self.adv_config_button_frame = uiw.uiQuietFrame(master=self.sim_frame) + self.adv_config_button_frame.pack( + side=tk.LEFT, fill=tk.BOTH, expand=True + ) - self.start_button = uiButton( + self.start_button = uiw.uiButton( master=self.start_button_frame, command=self.build_and_run_sim, text="Start Game", ) # Start Game Button - self.start_button.pack(side=tk.RIGHT, padx=50, fill=tk.BOTH, expand=True) + self.start_button.pack( + side=tk.RIGHT, padx=50, fill=tk.BOTH, expand=True + ) - self.adv_config_button = uiButton( + self.adv_config_button = uiw.uiButton( master=self.adv_config_button_frame, command=self.run_advanced_settings, text="Advanced Config", ) - self.adv_config_button.pack(side=tk.LEFT, padx=50, fill=tk.BOTH, expand=True) + self.adv_config_button.pack( + side=tk.LEFT, padx=50, fill=tk.BOTH, expand=True + ) ####################################################################### - ## MAP UI + # MAP UI ####################################################################### - self.maps_frame = uiQuietFrame(master=self) # map section + self.maps_frame = uiw.uiQuietFrame(master=self) # map section self.maps_frame.pack(side=tk.LEFT, fill=tk.BOTH, padx=10, pady=10) - self.maps_list_frame = uiQuietFrame(master=self.maps_frame) + self.maps_list_frame = uiw.uiQuietFrame(master=self.maps_frame) self.maps_list_frame.pack(side=tk.TOP, fill=tk.BOTH) - self.maps_label = uiLabel(master=self.maps_list_frame, text="MAPS") + self.maps_label = uiw.uiLabel(master=self.maps_list_frame, text="MAPS") self.maps_label.pack(side=tk.TOP, fill=tk.BOTH) - self.maps_list = uiListBox( + self.maps_list = uiw.uiListBox( self.maps_list_frame ) # this is the box that says Map 1 maps_list self.maps_list.pack(side=tk.TOP, fill=tk.BOTH, expand=True) @@ -155,24 +158,26 @@ def build_ui(self): "<>", self.select_map ) # this calls select_map(self, whateverMapIsSelectedInTheBox) - self.map_info_frame = uiQuietFrame(master=self.maps_frame) + self.map_info_frame = uiw.uiQuietFrame(master=self.maps_frame) self.map_info_frame.pack(side=tk.BOTTOM) - self.map_info_label = uiLabel( + self.map_info_label = uiw.uiLabel( master=self.map_info_frame, text="MAP INFORMATION" ) self.map_info_label.pack(side=tk.TOP, fill=tk.BOTH) - self.map_info_text = uiScrollText(self.map_info_frame) # info on selected frame + # info on selected frame + self.map_info_text = uiw.uiScrollText(self.map_info_frame) self.map_info_text.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True) self.map_info_text.insert(tk.END, "No map info") - self.update_map_names() # this function call gets Map 1 (later other maps) into the box + # this function call gets Map 1 (later other maps) into the box + self.update_map_names() ####################################################################### - ## TEAM UI + # TEAM UI ###################################################################### - self.team_frame = uiQuietFrame( + self.team_frame = uiw.uiQuietFrame( master=self ) # team frame is everything team UI #top line creates frame self.team_frame.pack_propagate(0) @@ -180,71 +185,92 @@ def build_ui(self): side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10 ) # bottom line is frame settings - self.team_pool_frame = uiQuietFrame( + self.team_pool_frame = uiw.uiQuietFrame( master=self.team_frame ) # Left half of team frame self.team_pool_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True) - self.team_pool_left_frame = uiQuietFrame( + self.team_pool_left_frame = uiw.uiQuietFrame( master=self.team_pool_frame ) # part of pool frame, pool section self.team_pool_left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) - self.team_pool_label = uiLabel( + self.team_pool_label = uiw.uiLabel( master=self.team_pool_left_frame, text="TEAM POOL" ) # label that says "Team Pool" section of pool left frame self.team_pool_label.pack(fill=tk.BOTH, side=tk.TOP, expand=True) - self.teams_list = uiListBox(self.team_pool_left_frame) + self.teams_list = uiw.uiListBox(self.team_pool_left_frame) self.teams_list.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True) - self.team_pool_right_frame = uiQuietFrame( + self.team_pool_right_frame = uiw.uiQuietFrame( master=self.team_pool_frame ) # pool right frame is the sides section, also part of Pool Frame - self.team_pool_right_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True) + self.team_pool_right_frame.pack( + side=tk.BOTTOM, fill=tk.BOTH, expand=True + ) - self.sides_label = uiLabel(master=self.team_pool_right_frame, text="SIDES") + self.sides_label = uiw.uiLabel( + master=self.team_pool_right_frame, text="SIDES" + ) self.sides_label.pack(fill=tk.BOTH, side=tk.TOP, expand=True) - self.sides_list = uiListBox(self.team_pool_right_frame) + self.sides_list = uiw.uiListBox(self.team_pool_right_frame) self.sides_list.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True) - self.play_pool_frame = uiQuietFrame( + # Assigned is other half of team frame, "Play pool" is teams + # that are playing + self.play_pool_frame = uiw.uiQuietFrame( master=self.team_frame - ) # Assigned is other half of team frame, "Play pool" is teams that are playing + ) + self.play_pool_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True) - self.play_pool_right_frame = uiQuietFrame(master=self.play_pool_frame) + self.play_pool_right_frame = uiw.uiQuietFrame( + master=self.play_pool_frame + ) self.play_pool_right_frame.pack( side=tk.RIGHT, fill=tk.BOTH, expand=True, pady=5 ) - self.play_pool_label = uiLabel( + self.play_pool_label = uiw.uiLabel( master=self.play_pool_right_frame, text="ASSIGNED TEAMS" ) self.play_pool_label.pack(fill=tk.BOTH, side=tk.TOP, expand=True) - self.sub_play_pool_frame = uiQuietFrame(master=self.play_pool_right_frame) - self.sub_play_pool_frame.pack(fill=tk.BOTH, side=tk.BOTTOM, expand=True) + self.sub_play_pool_frame = uiw.uiQuietFrame( + master=self.play_pool_right_frame + ) + self.sub_play_pool_frame.pack( + fill=tk.BOTH, side=tk.BOTTOM, expand=True + ) - self.team_assignments_list = uiListBox(self.sub_play_pool_frame) - self.team_assignments_list.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) + self.team_assignments_list = uiw.uiListBox(self.sub_play_pool_frame) + self.team_assignments_list.pack( + side=tk.RIGHT, fill=tk.BOTH, expand=True + ) self.update_team_names() self.pack(fill=tk.BOTH, expand=True) - self.add_team_button = uiButton( - master=self.play_pool_frame, text="Add Team >>>", command=self.add_team + self.add_team_button = uiw.uiButton( + master=self.play_pool_frame, + text="Add Team >>>", + command=self.add_team + ) + self.add_team_button.pack( + side=tk.TOP, fill=tk.BOTH, expand=True, pady=5 ) - self.add_team_button.pack(side=tk.TOP, fill=tk.BOTH, expand=True, pady=5) - self.remove_team_button = uiButton( + self.remove_team_button = uiw.uiButton( master=self.play_pool_frame, text="<<< Remove Team", command=self.remove_team, ) - self.remove_team_button.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True, pady=5) + self.remove_team_button.pack( + side=tk.BOTTOM, fill=tk.BOTH, expand=True, pady=5 + ) def add_team(self): """Adds team/side assignment to sim""" @@ -260,7 +286,9 @@ def add_team(self): self.sim.add_team_name(side_selection, team_name) self.update_team_names() else: - self.logger.error("App::add_team() - No side or team selected.") + self.logger.error( + "App::add_team() - No side or team selected." + ) else: self.logger.error("App::add_team() - Sim is missing the map.") @@ -277,10 +305,11 @@ def build_and_run_sim(self): """Builds and runs sim""" try: self.sim.build_sim(self.ldr) - except BuildException as e: + except zexceptions.BuildException as e: tk.messagebox.showinfo(title="Build Exception", message=e) else: - # tk.messagebox.showinfo(title="Success",message="Sim build was successful.") --this line makes a pop up + # tk.messagebox.showinfo(title="Success",message= + # "Sim build was successful.") --this line makes a pop up map_width = self.sim.get_map().get_data("width") map_height = self.sim.get_map().get_data("height") self.UIMap = ui_sim.UISim( @@ -295,4 +324,5 @@ def build_and_run_sim(self): def run_advanced_settings(self): """Opens advanced config settings""" - self.UIMap = ui_advanced_config.UISettings(self, self.logger) + pass + # self.UIMap = ui_advanced_config.UISettings(self, self.logger) diff --git a/ui_sim.py b/ui_sim.py index ddd9adb..39ec949 100644 --- a/ui_sim.py +++ b/ui_sim.py @@ -4,14 +4,10 @@ # The main UI element for a simulation in progress. ############################################################################## import tkinter as tk -from tkinter.font import Font -import tkinter.scrolledtext as scrolltext import queue -import cProfile -import logging import ui_scoreboard -from ui_widgets import * +import ui_widgets as uiw # The default delay between turns. This value is used by # both the continuous and the turn-by-turn modes. If the delay @@ -21,7 +17,14 @@ class UISim(tk.Toplevel): def __init__( - self, map_width, map_height, sim, omsgr, controller, master=None, logger=None + self, + map_width, + map_height, + sim, + omsgr, + controller, + master=None, + logger=None ): """Sets window and frame information and generates the sim UI @@ -33,7 +36,7 @@ def __init__( """ super().__init__(master) self.master = master - self.configure(bg=BGCOLOR) + self.configure(bg=uiw.BGCOLOR) self.title("MAIA - Sim UI") self.logger = logger self.controller = controller @@ -60,16 +63,16 @@ def __init__( self.continuous_run = False # Create the left and right frames - self.map_frame = uiQuietFrame(master=self) + self.map_frame = uiw.uiQuietFrame(master=self) self.map_frame.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) - self.log_frame = uiQuietFrame(master=self) + self.log_frame = uiw.uiQuietFrame(master=self) self.log_frame.pack(fill=tk.BOTH, expand=True, side=tk.RIGHT) # Create the map canvas self.x_bar = tk.Scrollbar(self.map_frame, orient=tk.HORIZONTAL) self.y_bar = tk.Scrollbar(self.map_frame, orient=tk.VERTICAL) - self.canvas = uiCanvas( + self.canvas = uiw.uiCanvas( master=self.map_frame, width=800, height=800, @@ -97,12 +100,12 @@ def __init__( # Create the log notebook and tabs - self.log_notebook = uiNotebook(master=self.log_frame) + self.log_notebook = uiw.uiNotebook(master=self.log_frame) self.log_notebook.pack(fill=tk.BOTH, expand=True, side=tk.TOP) - self.main_log_frame = uiQuietFrame(master=self.log_notebook) + self.main_log_frame = uiw.uiQuietFrame(master=self.log_notebook) self.log_notebook.add(self.main_log_frame, text="Main") - self.main_log_scroll = uiScrollText(master=self.main_log_frame) + self.main_log_scroll = uiw.uiScrollText(master=self.main_log_frame) self.main_log_scroll.pack(fill=tk.BOTH, expand=True, side=tk.TOP) self.main_log_scroll.configure(state="disabled") @@ -111,41 +114,57 @@ def __init__( # self.data_frame_2 = uiQuietFrame(master=self.log_frame) # self.data_frame_2.pack(fill=tk.BOTH,expand=True,side=tk.TOP) - self.btn_frame_1 = uiQuietFrame(master=self.log_frame) + self.btn_frame_1 = uiw.uiQuietFrame(master=self.log_frame) self.btn_frame_1.pack(fill=tk.BOTH, expand=True, side=tk.TOP) - self.btn_run = uiButton( - master=self.btn_frame_1, text="Run", command=self.run_continuous_proxy + self.btn_run = uiw.uiButton( + master=self.btn_frame_1, + text="Run", + command=self.run_continuous_proxy ) self.btn_run.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) - self.btn_pause = uiButton( - master=self.btn_frame_1, text="Pause", command=self.pause_continuous + self.btn_pause = uiw.uiButton( + master=self.btn_frame_1, + text="Pause", + command=self.pause_continuous ) self.btn_pause.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) - self.delay_label = uiLabel(master=self.btn_frame_1, text="Delay (ms)") + self.delay_label = uiw.uiLabel( + master=self.btn_frame_1, text="Delay (ms)" + ) self.delay_label.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) - self.delay_entry = uiEntry(master=self.btn_frame_1) + self.delay_entry = uiw.uiEntry(master=self.btn_frame_1) self.delay_entry.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) - self.btn_frame_2 = uiQuietFrame(master=self.log_frame) + self.btn_frame_2 = uiw.uiQuietFrame(master=self.log_frame) self.btn_frame_2.pack(fill=tk.BOTH, expand=True, side=tk.TOP) - self.turns_button = uiButton( - master=self.btn_frame_2, text="Run X Turns", command=self.run_x_turns + self.turns_button = uiw.uiButton( + master=self.btn_frame_2, + text="Run X Turns", + command=self.run_x_turns ) self.turns_button.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) - self.turns_label = uiLabel(master=self.btn_frame_2, text="Turns To Run") + self.turns_label = uiw.uiLabel( + master=self.btn_frame_2, text="Turns To Run" + ) self.turns_label.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) - self.turns_entry = uiEntry(master=self.btn_frame_2) + self.turns_entry = uiw.uiEntry(master=self.btn_frame_2) self.turns_entry.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) - self.btn_frame_3 = uiQuietFrame(master=self.log_frame) + self.btn_frame_3 = uiw.uiQuietFrame(master=self.log_frame) self.btn_frame_3.pack(fill=tk.BOTH, expand=True, side=tk.TOP) - self.display_points_button = uiButton( - master=self.btn_frame_3, text="Display Points", command=self.display_points + self.display_points_button = uiw.uiButton( + master=self.btn_frame_3, + text="Display Points", + command=self.display_points + ) + self.display_points_button.pack( + fill=tk.BOTH, expand=True, side=tk.LEFT ) - self.display_points_button.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) - self.end_game_button = uiButton( - master=self.btn_frame_3, text="End Game", command=self.display_scoreboard + self.end_game_button = uiw.uiButton( + master=self.btn_frame_3, + text="End Game", + command=self.display_scoreboard ) self.end_game_button.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) @@ -366,10 +385,14 @@ def display_scoreboard(self): for widget in self.log_frame.winfo_children(): widget.pack_forget() - scoreboard_frame = uiQuietFrame(master=self.log_frame) + scoreboard_frame = uiw.uiQuietFrame(master=self.log_frame) scoreboard_frame.pack(fill=tk.BOTH, expand=True) scoreboard_frame = ui_scoreboard.ScoreboardFrame( - teams_scores, self.controller, self, self.sim, master=self.log_frame + teams_scores, + self.controller, + self, + self.sim, + master=self.log_frame ) scoreboard_frame.pack(fill=tk.BOTH, expand=True) diff --git a/ui_team_config.py b/ui_team_config.py new file mode 100644 index 0000000..3657f33 --- /dev/null +++ b/ui_team_config.py @@ -0,0 +1,416 @@ +import tkinter as tk +import ui_widgets as uiw + + +class UITeamConfig(tk.Frame): + def __init__(self, controller, ldr, master=None, logger=None): + super().__init__(master) + self.controller = controller + self.master = master + self.configure(bg=uiw.BGCOLOR) + self.logger = logger + self.ldr = ldr + self.build_ui() + self.ui_map = None + + def validate_number_entry(self, input): + """ + Validates each entered value (input) to ensure it is a number. + """ + input.replace(".", "", 1) + input.replace("-", "", 1) + if input.isdigit() or input == "" or "-" in input or "." in input: + return True + + else: + return False + + def get_focused_entry(self): + """ + Returns the currently focused entry in advanced config. + """ + focused_entry = self.focus_get() + return focused_entry + + def build_ui(self): + """ + Initializes all widgets and places them. + """ + # Make main frames + + self.main_frame = uiw.uiQuietFrame(master=self) + self.team_selection_column = uiw.uiLabelFrame( + master=self.main_frame, text="Teams" + ) + self.teams_column = uiw.uiLabelFrame( + master=self.main_frame, text="Team Info" + ) + self.agents_column = uiw.uiLabelFrame( + master=self.main_frame, text="Agent Info" + ) + self.button_row = uiw.uiQuietFrame(master=self.main_frame) + self.title_label = uiw.uiLabel( + master=self.main_frame, text="Team Config" + ) + self.validate_num = self.main_frame.register( + self.validate_number_entry + ) + + # Place frames + self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + self.title_label.pack(side=tk.TOP, fill="x") + self.button_row.pack(side=tk.BOTTOM, fill="x") + self.team_selection_column.pack(side=tk.LEFT, fill="y") + self.teams_column.pack(side=tk.LEFT, fill="y") + self.agents_column.pack(side=tk.LEFT, fill="y") + + # Team Selection Widgets + self.select_team_listbox_var = tk.StringVar() + self.select_team_listbox = uiw.uiListBox( + master=self.team_selection_column, + listvariable=self.select_team_listbox_var, + selectmode="browse", + ) + self.select_team_listbox.pack(side=tk.LEFT, fill=tk.BOTH) + + # Team Info Widgets + self.team_size_label = uiw.uiLabel( + master=self.teams_column, text="Size:" + ) + self.team_size_entry = uiw.EntryHelp( + master=self.teams_column, + text=( + "The team size field represents how many agents you want " + + "in the selected team." + " This field takes numeric values only." + ), + ) + self.team_size_entry.entry.config( + validate="all", validatecommand=(self.validate_num, "%P") + ) + self.team_name_label = uiw.uiLabel( + master=self.teams_column, text="Name:" + ) + self.team_name_entry = uiw.EntryHelp( + master=self.teams_column, text="To be added." + ) + self.agent_list_label = uiw.uiLabel( + master=self.teams_column, text="Agents" + ) + self.agent_listbox_var = tk.StringVar() + self.agent_listbox = uiw.uiListBox( + master=self.teams_column, + listvariable=self.agent_listbox_var, + selectmode="browse", + ) + self.add_agent_button = uiw.uiButton( + master=self.teams_column, command=self.add_agent, text="Add Agent" + ) + self.del_agent_button = uiw.uiCarefulButton( + master=self.teams_column, + command=self.del_agent, + text="Delete Agent" + ) + self.update_agent_button = uiw.uiButton( + master=self.teams_column, + command=self.update_agent, + text="Update Agent" + ) + + self.select_team_listbox.bind( + "<>", self.cmd_new_team_selection + ) + self.agent_listbox.bind("<>", self.cmd_show_agent) + + self.team_size_label.grid(row=0, column=0, sticky="ew") + self.team_size_entry.frame.grid(row=0, column=1, sticky="ew") + self.team_name_label.grid(row=1, column=0, sticky="ew") + self.team_name_entry.frame.grid(row=1, column=1, sticky="ew") + self.agent_list_label.grid(row=2, column=0, columnspan=2, sticky="ew") + self.agent_listbox.grid(row=3, column=0, columnspan=2, sticky="ew") + self.add_agent_button.grid(row=4, column=0, columnspan=2, sticky="ew") + self.update_agent_button.grid( + row=5, column=0, columnspan=2, sticky="ew" + ) + self.del_agent_button.grid(row=6, column=0, columnspan=2, sticky="ew") + + # Agent Info Widgets + self.callsign_label = uiw.uiLabel( + master=self.agents_column, text="Callsign:" + ) + self.callsign_entry = uiw.EntryHelp( + master=self.agents_column, text="To be added." + ) + self.squad_label = uiw.uiLabel( + master=self.agents_column, text="Squad:" + ) + self.squad_entry = uiw.EntryHelp( + master=self.agents_column, text="To be added." + ) + self.agent_object_label = uiw.uiLabel( + master=self.agents_column, text="Object:" + ) + self.agent_object_entry = uiw.EntryHelp( + master=self.agents_column, text="To be added." + ) + self.ai_file_label = uiw.uiLabel( + master=self.agents_column, text="AI File:" + ) + self.ai_file_entry = uiw.EntryHelp( + master=self.agents_column, text="To be added." + ) + + self.callsign_label.grid(row=1, column=1, sticky="ew") + self.callsign_entry.frame.grid(row=1, column=2, sticky="ew") + self.squad_label.grid(row=2, column=1, sticky="ew") + self.squad_entry.frame.grid(row=2, column=2, sticky="ew") + self.agent_object_label.grid(row=3, column=1, sticky="ew") + self.agent_object_entry.frame.grid(row=3, column=2, sticky="ew") + self.ai_file_label.grid(row=4, column=1, sticky="ew") + self.ai_file_entry.frame.grid(row=4, column=2, sticky="ew") + + # Team Buttons + + self.teams_create_button = uiw.uiButton( + master=self.button_row, command=self.create_team, text="Add Team" + ) + self.teams_create_button.pack(side=tk.LEFT) + self.teams_update_button = uiw.uiButton( + master=self.button_row, + command=self.update_team, + text="Update Team" + ) + self.teams_update_button.pack(side=tk.LEFT) + self.teams_delete_button = uiw.uiCarefulButton( + master=self.button_row, + command=self.delete_team, + text="Delete Team" + ) + self.teams_delete_button.pack(side=tk.LEFT) + + # High-level Buttons + self.home_button = uiw.uiButton( + master=self.button_row, command=self.goto_home, text="Home" + ) + self.home_button.pack(side=tk.RIGHT) + self.save_to_json_button = uiw.uiCarefulButton( + master=self.button_row, + command=self.save_to_json, + text="Save Teams to JSON" + ) + self.save_to_json_button.pack(side=tk.RIGHT) + + self.populate_team_listbox() + + def populate_team_listbox(self): + """ + Gets information from the loader and assigns current values + for each setting type. + """ + team_names = sorted(self.ldr.get_team_names()) + self.select_team_listbox.delete(0, tk.END) + self.select_team_listbox_var.set(team_names) + + def get_previous_team_combo(self, event): + pass + # self.prev_team_combo = self.select_team_listbox.current() + + def cmd_new_team_selection(self, event=None): + """ + Gets the correct team data for the currently selected team. + """ + self.show_team_entry() + + def show_team_entry(self): + """ + Updates the values stored in the team entry widgets. + """ + current_team = self.get_currently_selected_team() + if current_team is not None: + + self.team_name_entry.entry.delete(0, tk.END) + self.team_name_entry.entry.insert(0, current_team["name"]) + self.team_size_entry.entry.delete(0, tk.END) + self.team_size_entry.entry.insert(0, current_team["size"]) + + self.agent_listbox.delete(0, tk.END) + self.clear_agent_info() + + agent_defs = current_team["agent_defs"] + agent_callsigns = [] + for agent in agent_defs: + agent_callsigns.append(agent["callsign"]) + self.agent_listbox_var.set(agent_callsigns) + + def cmd_show_agent(self, event=None): + self.show_agent_entry() + + def clear_agent_info(self): + + self.callsign_entry.entry.delete(0, tk.END) + self.squad_entry.entry.delete(0, tk.END) + self.agent_object_entry.entry.delete(0, tk.END) + self.ai_file_entry.entry.delete(0, tk.END) + + def show_agent_entry(self): + current_team = self.get_currently_selected_team() + if current_team is not None: + self.clear_agent_info() + index = self.agent_listbox.curselection() + if len(index) == 1: + index = index[0] + self.callsign_entry.entry.insert( + 0, current_team["agent_defs"][index]["callsign"] + ) + self.squad_entry.entry.insert( + 0, current_team["agent_defs"][index]["squad"] + ) + self.agent_object_entry.entry.insert( + 0, current_team["agent_defs"][index]["object"] + ) + self.ai_file_entry.entry.insert( + 0, current_team["agent_defs"][index]["AI_file"] + ) + + # CREATE NEW + def create_team(self): + """ + Creates a new team and adds it to the team dictionary. + """ + + team_data = self.ldr.get_team_templates() + + good_name = False + while not good_name: + team_id = tk.simpledialog.askstring( + "New Team ID", "Please enter an ID for the new team." + ) + if len(team_id) == 0: + tk.messagebox.showwarning( + "Warning", "You must enter a team ID to continue" + ) + elif team_id in team_data.keys(): + tk.messagebox.showwarning( + "Warning", "This ID already exists, please enter a new ID." + ) + else: + good_name = True + + self.select_team_listbox.insert(tk.END, team_id) + + new_team_data = { + "size": "0", + "name": team_id, + "agent_defs": [], + } + team_data.update({team_id: new_team_data}) + + index = self.select_team_listbox.size() - 1 + self.select_team_listbox.selection_clear(0, tk.END) + self.select_team_listbox.selection_set(index) + self.select_team_listbox.activate(index) + self.cmd_new_team_selection() + + # UPDATE JSON FILES + def update_team(self): + """ + Updates the teams JSON values. + """ + team_data = self.ldr.get_team_templates() + current_team = self.get_currently_selected_team() + if current_team is not None: + new_name = self.team_name_entry.entry.get() + if new_name != current_team["name"]: + if new_name in team_data.keys(): + tk.simpledialog.showwarning( + title="Warning", + message=f"{new_name} is in use by another team. " + + "Please use another name.", + ) + else: + old_name = current_team["name"] + current_team["name"] = new_name + del team_data[old_name] + + team_data[new_name] = current_team + self.populate_team_listbox() + + # DELETE + + def delete_team(self): + """ + Deletes the currently selected team from the JSON and team dictionary. + """ + team_data = self.ldr.get_team_templates() + current_team = self.get_currently_selected_team() + if current_team is not None: + del team_data[current_team["name"]] + self.populate_team_listbox() + + def add_agent(self, event=None): + current_team = self.get_currently_selected_team() + if current_team is not None: + cur_size_of_team = len(current_team["agent_defs"]) + cur_size_of_team = int(cur_size_of_team) + new_agent = {} + new_agent["callsign"] = f"New Agent {cur_size_of_team}" + new_agent["squad"] = "Missing" + new_agent["object"] = "Missing" + new_agent["AI_file"] = "Missing" + current_team["agent_defs"].append(new_agent) + self.agent_listbox.insert(tk.END, new_agent["callsign"]) + self.agent_listbox.selection_clear(0, tk.END) + self.agent_listbox.selection_set(self.agent_listbox.size() - 1) + self.agent_listbox.activate(self.agent_listbox.size() - 1) + self.show_agent_entry() + + def del_agent(self, event=None): + current_team = self.get_currently_selected_team() + if current_team is not None: + agent_index = self.agent_listbox.curselection() + if len(agent_index) == 1: + self.agent_listbox.delete(agent_index[0]) + del current_team["agent_defs"][agent_index[0]] + self.agent_listbox.selection_clear(0, tk.END) + self.show_agent_entry() + + def update_agent(self, event=None): + current_team = self.get_currently_selected_team() + if current_team is not None: + agent_index = self.agent_listbox.curselection() + agent = current_team["agent_defs"][agent_index[0]] + if len(agent_index) == 1: + callsign = self.callsign_entry.entry.get() + squad = self.squad_entry.entry.get() + object_name = self.agent_object_entry.entry.get() + ai_file = self.ai_file_entry.entry.get() + if ( + len(callsign) == 0 + or len(squad) == 0 + or len(object_name) == 0 + or len(ai_file) == 0 + ): + tk.messagebox.showwarning( + "Warning", "Cannot have blank agent fields." + ) + else: + agent["callsign"] = callsign + agent["squad"] = squad + agent["object"] = object_name + agent["AI_file"] = ai_file + self.agent_listbox.delete(agent_index[0]) + self.agent_listbox.insert(agent_index[0], callsign) + + def get_currently_selected_team(self): + team_index = self.select_team_listbox.curselection() + if len(team_index) == 1: + team_name = self.select_team_listbox.get(team_index[0]) + return self.ldr.get_team_template(team_name) + else: + return None + + def save_to_json(self): + self.ldr.save_team_templates() + + def goto_home(self): + self.controller.show_frame("home_page") diff --git a/ui_widgets.py b/ui_widgets.py index 147f43e..c4272f0 100644 --- a/ui_widgets.py +++ b/ui_widgets.py @@ -5,26 +5,26 @@ ############################################################################## import tkinter as tk +import tkinter.scrolledtext from tkinter import messagebox -from tkinter.font import Font from tkinter import ttk -import tkinter.scrolledtext as scrolltext import platform -from packages.tkmacosx import Button -from PIL import ImageTk, Image +# from packages.tkmacosx import Button +from PIL import ImageTk, Image +import sprite_manager +# Constants BGCOLOR = "#E5E5E5" TEXTCOLOR = "#222222" - BTN_DARK = "#495EA7" BTN_LIGHT = "#FFFFFF" - +CAREFUL_BTN_DARK = "red" ENTRY_DARK = "#222222" ENTRY_BG = "salmon1" - BOXFILLCOLOR = "#B2C1E3" - +PADX = 5 +PADY = 5 global_sprite_list = [] @@ -35,10 +35,12 @@ class uiListBox(tk.Listbox): - def __init__(self, master=None): - super().__init__(master) + def __init__(self, master=None, **kwargs): + super().__init__(master, kwargs) + if "selectmode" not in kwargs: + kwargs["selectmode"] = tk.SINGLE self.config( - selectmode=tk.SINGLE, + selectmode=kwargs["selectmode"], bg=BOXFILLCOLOR, fg=TEXTCOLOR, highlightthickness=1, @@ -67,7 +69,45 @@ def __init__(self, master=None): ) -class uiButton(Button): +class uiScrollFrame(tk.Frame): + def __init__(self, master): + super().__init__(master, cursor="arrow") + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + self.canvas = tk.Canvas(self) + self.canvas.grid(row=0, column=0, sticky="ns") + self.scroll_y = tk.Scrollbar( + self, orient="vertical", command=self.canvas.yview + ) + self.scroll_y.grid(row=0, column=1, sticky="ns") + self.sub_frame = tk.Frame(self.canvas) + self.canvas.create_window( + 0, 0, anchor="nw", window=self.sub_frame, tag="window" + ) + self.sub_frame.bind("", self.config_sub_frame) + self.canvas.bind("", self.config_scrollframe) + # self.canvas.bind("", self.on_mouse_wheel) + + def focus(self, event): + self.canvas.bind_all("", self.on_mouse_wheel) + + def unfocus(self, event): + self.canvas.unbind_all("") + + def config_sub_frame(self, event): + self.canvas.configure( + scrollregion=self.canvas.bbox("all"), + yscrollcommand=self.scroll_y.set + ) + + def config_scrollframe(self, event): + event.widget.itemconfig("window", width=event.width) + + def on_mouse_wheel(self, event): + self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") + + +class uiButton(tk.Button): def __init__(self, **kwargs): super().__init__(kwargs["master"]) self.config( @@ -85,20 +125,51 @@ def __init__(self, **kwargs): ) -class uiLabel(tk.Label): +class uiCarefulButton(tk.Button): def __init__(self, **kwargs): super().__init__(kwargs["master"]) self.config( text=kwargs["text"], - bg=BGCOLOR, - fg=TEXTCOLOR, + command=kwargs["command"], + bg=CAREFUL_BTN_DARK, + fg=BTN_LIGHT, + activeforeground=CAREFUL_BTN_DARK, + activebackground=BTN_LIGHT, highlightthickness=1, - highlightbackground=TEXTCOLOR, - highlightcolor=TEXTCOLOR, + highlightbackground=BTN_LIGHT, + highlightcolor=BTN_LIGHT, + relief=tk.FLAT, font=("Arial", FONT_SIZE), ) +class uiLabel(tk.Label): + def __init__(self, **kwargs): + super().__init__(kwargs["master"]) + + if "text" in kwargs: + if "font" not in kwargs: + kwargs["font"] = ("Arial", FONT_SIZE) + self.config( + text=kwargs["text"], + bg=BGCOLOR, + fg=TEXTCOLOR, + # highlightthickness=1, + # highlightbackground=TEXTCOLOR, + # highlightcolor=TEXTCOLOR, + font=kwargs["font"], + ) + elif "image" in kwargs: + self.config( + image=kwargs["image"], + bg=BGCOLOR, + fg=TEXTCOLOR, + # highlightthickness=1, + # highlightbackground=TEXTCOLOR, + # highlightcolor=TEXTCOLOR, + ) + + class uiTextbox(tk.Text): def __init__(self, **kwargs): super().__init__(kwargs.pop("master"), **kwargs) @@ -113,6 +184,24 @@ def __init__(self, **kwargs): borderwidth=0, highlightthickness=0, highlightbackground=BGCOLOR, + padx=PADX, + pady=PADY, + ) + + +class uiLabelFrame(tk.LabelFrame): + def __init__(self, **kwargs): + super().__init__(kwargs["master"]) + self.config( + relief=tk.FLAT, + borderwidth=0, + highlightthickness=0, + highlightbackground=BGCOLOR, + text=kwargs["text"], + labelanchor="n", + font=("Arial", 15), + padx=PADX, + pady=PADY, ) @@ -155,40 +244,45 @@ def __init__(self, master, text): self.frame = uiQuietFrame(master=master) self.frame.grid(sticky="nsew") - self.frame.columnconfigure(7) + # self.frame.columnconfigure(2) self.entry = uiEntry(master=self.frame) - self.entry.grid(row=0, column=0, columnspan=6) + self.entry.configure(disabledforeground="black") + self.entry.grid(row=0, column=0) self.help_button = uiButton( master=self.frame, text="?", - command=lambda: messagebox.showinfo("Help", self.text, parent=self.master), + command=lambda: messagebox.showinfo( + "Help", self.text, parent=self.master + ), ) - self.help_button.configure(width=26) - self.help_button.grid(row=0, column=7) + self.help_button.configure(width=1) + self.help_button.grid(row=0, column=1) class ComboBoxHelp: - def __init__(self, master, text): + def __init__(self, master, text, **kwargs): self.master = master self.text = text self.frame = uiQuietFrame(master=master) self.frame.grid(sticky="nsew") - self.frame.columnconfigure(7) + # self.frame.columnconfigure(7) - self.combobox = uiComboBox(master=self.frame) - self.combobox.grid(row=0, column=0, columnspan=6) + self.combobox = uiComboBox(master=self.frame, **kwargs) + self.combobox.grid(row=0, column=0) self.help_button = uiButton( master=self.frame, text="?", - command=lambda: messagebox.showinfo("Help", self.text, parent=self.master), + command=lambda: messagebox.showinfo( + "Help", self.text, parent=self.master + ), ) - self.help_button.configure(width=26) - self.help_button.grid(row=0, column=7) + self.help_button.configure(width=1) + self.help_button.grid(row=0, column=1) class uiComboBox(ttk.Combobox): @@ -201,6 +295,7 @@ class uiCanvas(tk.Canvas): def __init__(self, **kwargs): super().__init__(kwargs["master"]) self.cell_size = kwargs["cell_size"] + self.border_size = kwargs["border_size"] self.obj_char_size = kwargs["obj_char_size"] self.item_char_size = kwargs["item_char_size"] self.char_offset = kwargs["char_offset"] @@ -209,6 +304,9 @@ def __init__(self, **kwargs): self.rcfont = tk.font.Font( family="TkFixedFont", size=int(self.obj_char_size / 2) ) + self.map_font = tk.font.Font( + family="TkFixedFont", size=int(self.cell_size - 10) + ) self.config( width=kwargs["width"], height=kwargs["height"], @@ -220,70 +318,115 @@ def __init__(self, **kwargs): highlightbackground=TEXTCOLOR, highlightcolor=TEXTCOLOR, ) + self.sprites = [] + self.tiles = {} + self.objects = {} + self.items = {} + self.starting_locations = {} + + def mousexy_to_cellxy(self, x, y): + x = int((self.canvasx(x) - self.border_size) // self.cell_size) + y = int((self.canvasy(y) - self.border_size) // self.cell_size) + return x, y def draw_tile(self, **kwargs): """Draws tile""" - x0 = kwargs["x"] * self.cell_size - y0 = kwargs["y"] * self.cell_size + x = kwargs["x"] + y = kwargs["y"] + x0 = x * self.cell_size + self.border_size + y0 = y * self.cell_size + self.border_size x1 = x0 + self.cell_size y1 = y0 + self.cell_size - return self.create_rectangle(x0, y0, x1, y1, fill=kwargs["fill"]) + tile = self.create_rectangle( + x0, y0, x1, y1, fill=kwargs["fill"], width=2 + ) + self.tiles[(x, y)] = tile + + def draw_circle(self, x, y, color, diameter): + x0 = x * self.cell_size + self.border_size \ + + ((self.cell_size - diameter) // 2) + y0 = y * self.cell_size + self.border_size \ + + ((self.cell_size - diameter) // 2) + x1 = x0 + diameter + y1 = y0 + diameter + return self.create_oval(x0, y0, x1, y1, fill=color) + + def draw_character(self, x, y, character, color): + x0 = x * self.cell_size + self.border_size + (self.cell_size // 2) + # Magic number 4 almost certainly does not work for other font sizes. + y0 = y * self.cell_size + self.border_size \ + + ((self.cell_size - 4) // 2) + return self.create_text( + x0, y0, text=character, fill=color, font=self.map_font + ) - # self._create( - # 'rectangle', - # [x0,y0,x1,y1], - # {'fill':kwargs['fill']} - # ) + def draw_starting_location(self, x, y, loc_num, color): + circ_id = self.draw_circle(x, y, color, self.cell_size - 5) + char_id = self.draw_character(x, y, str(loc_num), "black") + self.starting_locations[(x, y)] = (circ_id, char_id) - def draw_obj(self, **kwargs): + def remove_all_starting_locations(self): + for sl in self.starting_locations.values(): + self.remove_start(sl) + self.starting_locations = {} + + def draw_sprite(self, **kwargs): """Draws object Tries to draw sprite based on object status Defaults to text based representation if unsuccessful """ - # This function displays the object in the UI - dd = kwargs["dd"] # what to draw - x = ( # x coord - dd["x"] * self.cell_size - + self.obj_char_size / 2 - + self.char_offset - + self.cell_size - ) - y = ( # y coord - dd["y"] * self.cell_size - + self.obj_char_size / 2 - + self.char_offset - + self.cell_size - ) + x = kwargs["x"] * self.cell_size + self.border_size # x coord + y = kwargs["y"] * self.cell_size + self.border_size # y coord + + sprite_type = kwargs["sprite_type"] try: - if dd["alive"] is True: - self.sprite = Image.open(dd["sprite_path"]) - facing = ( # sim uses clock-wise coords, ui uses counter-clockwise coords - dd["facing"] * -1 - ) - global_sprite_list.append(self.sprite.copy()) - global_sprite_list[-1] = global_sprite_list[-1].rotate(facing) - global_sprite_list[-1] = ImageTk.PhotoImage(global_sprite_list[-1]) - return self.create_image(x, y, image=global_sprite_list[-1]) - else: - self.sprite = Image.open(dd["death_sprite_path"]) - facing = ( # sim uses clock-wise coords, ui uses counter-clockwise coords - dd["facing"] * -1 - ) - global_sprite_list.append(self.sprite.copy()) - global_sprite_list[-1] = global_sprite_list[-1].rotate(facing) - global_sprite_list[-1] = ImageTk.PhotoImage(global_sprite_list[-1]) - return self.create_image(x, y, image=global_sprite_list[-1]) - except: - return self.create_text( - x, y, text=dd["text"], fill=dd["fill"], font=self.obj_font - ) + sprite = sprite_manager.load_image(kwargs["sprite_filename"]) + width, height = sprite.size + x += (width // 2) + ((self.cell_size - width) // 2) + y += (height // 2) + ((self.cell_size - height) // 2) + facing = 0 + if "facing" in kwargs: + # sim uses clock-wise coords, ui uses counter-clockwise coords + facing = kwargs["facing"] * -1 + sprite.rotate(facing) + tk_sprite = ImageTk.PhotoImage(sprite) + self.sprites.append(tk_sprite) + sprite_id = self.create_image(x, y, image=tk_sprite) + + match (sprite_type): + case "object": + self.objects[(kwargs["x"], kwargs["y"])] = sprite_id + case "item": + self.items[(kwargs["x"], kwargs["y"], kwargs["id"])] \ + = sprite_id + case "start": + self.starting_locations[(kwargs["x"], kwargs["y"])] \ + = sprite_id + except Exception: + raise + + def remove_obj_at(self, x, y): + self.remove_obj(self.objects[(x, y)]) def remove_obj(self, obj_id): """Removes object""" self.delete(obj_id) + def remove_item_at(self, x, y, _id): + self.remove_item(self.items[(x, y, _id)]) + + def remove_item(self, item_id): + self.delete(item_id) + + def remove_start_at(self, x, y): + self.remove_start(self.starting_locations[(x, y)]) + + def remove_start(self, start_ids): + self.delete(start_ids[0]) + self.delete(start_ids[1]) + def redraw_obj(self, **kwargs): """Redraws object""" self.remove_obj(kwargs["obj_id"]) @@ -331,7 +474,7 @@ def draw_item(self, **kwargs): global_sprite_list.append(self.sprite.copy()) global_sprite_list[-1] = ImageTk.PhotoImage(global_sprite_list[-1]) return self.create_image(x, y, image=global_sprite_list[-1]) - except: + except Exception: return self.create_text( x, y, text=dd["text"], fill=dd["fill"], font=self.obj_font ) @@ -353,21 +496,94 @@ def update_drawn_item(self, **kwargs): ) self.coords(kwargs["itemID"], x, y) + def draw_row_labels(self, **kwargs): + width = kwargs["width"] + height = kwargs["height"] + + label_ids = [] + + top = self.obj_char_size / 2 + self.char_offset + bottom = ( + height * self.cell_size + + self.obj_char_size / 2 + + self.char_offset + + self.border_size + ) + + for i in range(width): + x = ( + i * self.cell_size + + self.obj_char_size / 2 + + self.char_offset + + self.border_size + ) + label_ids.append( + self.create_text( + x, top, text=str(i), fill=kwargs["fill"], font=self.rcfont + ) + ) + + label_ids.append( + self.create_text( + x, + bottom, + text=str(i), + fill=kwargs["fill"], + font=self.rcfont + ) + ) + + return label_ids + + def draw_column_labels(self, **kwargs): + width = kwargs["width"] + height = kwargs["height"] + + label_ids = [] + + left = self.obj_char_size / 2 + self.char_offset + right = ( + width * self.cell_size + + self.obj_char_size / 2 + + self.char_offset + + self.border_size + ) + + for i in range(height): + y = ( + i * self.cell_size + + self.obj_char_size / 2 + + self.char_offset + + self.border_size + ) + label_ids.append( + self.create_text( + left, + y, + text=str(i), + fill=kwargs["fill"], + font=self.rcfont + ) + ) + + label_ids.append( + self.create_text( + right, + y, + text=str(i), + fill=kwargs["fill"], + font=self.rcfont + ) + ) + + return label_ids + def draw_rc_number(self, **kwargs): """Draws RDNumber""" - x = kwargs["x"] * self.cell_size + self.obj_char_size / 2 + self.char_offset - y = kwargs["y"] * self.cell_size + self.obj_char_size / 2 + self.char_offset + x = kwargs["x"] * self.cell_size + self.obj_char_size / 2 \ + + self.char_offset + y = kwargs["y"] * self.cell_size + self.obj_char_size / 2 \ + + self.char_offset return self.create_text( x, y, text=kwargs["text"], fill=kwargs["fill"], font=self.rcfont ) - - # Circumvents a tk call...speed up is marginal. - # self._create( - # 'text', - # [x,y], - # { - # 'text':kwargs['text'], - # 'fill':kwargs['fill'], - # 'font':self.rcfont - # } - # ) diff --git a/valid.py b/valid.py index fde318c..e97797d 100644 --- a/valid.py +++ b/valid.py @@ -1,7 +1,8 @@ ############################################################################## # VALID # -# Is used to validate AI commands returning to the simulation. This makes it easier +# Is used to validate AI commands returning to the simulation. This makes it +# easier # on the rest of the code if we've weeded out badly formed commands before they # ever hit the processing part. ############################################################################## @@ -17,7 +18,9 @@ def __init__(self): self.commands["SET_TURNRATE"] = {"turnrate": [int, float]} self.commands["ACTIVATE_RADAR"] = {} self.commands["DEATIVATE_RADAR"] = {} - self.commands["BROADCAST"] = {"message": [int, float, str, list, tuple, dict]} + self.commands["BROADCAST"] = {"message": [ + int, float, str, list, tuple, dict + ]} self.commands["SET_RANGE"] = {"range": [int, float]} self.commands["TAKE_ITEM"] = { "item_name": [str, type(None)], @@ -47,13 +50,11 @@ def validate_commands(self, cmds): # Not a dictionary, remove if type(slot_dict) is not dict: bad_cmds1.append(tick) - # log.LogDebug("COMMAND VALIDATOR: Not a dictionary\n"+str(slot_dict)) continue # Empty dict, remove if len(slot_dict) == 0: bad_cmds1.append(tick) - # log.LogDebug("COMMAND VALIDATOR: Empty dictionary\n"+str(slot_dict)) continue bad_cmds2 = [] @@ -63,18 +64,15 @@ def validate_commands(self, cmds): # Command isn't a dict. if type(command) is not dict: bad_cmds2.append(slot_id) - # log.LogDebug("COMMAND VALIDATOR: Command is not a dictionary\n"+str(command)) continue # Command is missing the command entry if "command" not in command: bad_cmds2.append(slot_id) - # log.LogDebug("COMMAND VALIDATOR: Command is missing command entry") continue if command["command"] not in self.commands: bad_cmds2.append(slot_id) - # log.LogDebug("COMMAND VALIDATOR: Command "+str(command['command'])+" is not a valid command.") continue cmd_format = self.commands[command["command"]] @@ -82,7 +80,6 @@ def validate_commands(self, cmds): # command contains extra junk if len(cmd_format) + 1 != len(command): bad_cmds2.append(slot_id) - # log.LogDebug("COMMAND VALIDATOR: Command contains extra stuff.") continue for k, v in cmd_format.items(): @@ -90,12 +87,11 @@ def validate_commands(self, cmds): # Required command info is missing if k not in command: bad_cmds2.append(slot_id) - # log.LogDebug("COMMAND VALIDATOR: Required info is missing") break - # If the command info is of the wrong data type, throw it away. + # If the command info is of the wrong data type, + # throw it away. if type(command[k]) not in v: - # log.LogDebug("COMMAND VALIDATOR: Data '"+str(k)+"' has wrong type "+str(type(command[k]))) bad_cmds2.append(slot_id) break @@ -104,7 +100,8 @@ def validate_commands(self, cmds): for bc in bad_cmds2: del cmds[tick][bc] - # We might have emptied all commands for this tick. Check for empty again. + # We might have emptied all commands for this tick. Check for + # empty again. # Empty dict, remove if len(slot_dict) == 0: bad_cmds1.append(tick) diff --git a/zfunctions.py b/zfunctions.py index 92946de..473697a 100644 --- a/zfunctions.py +++ b/zfunctions.py @@ -3,7 +3,7 @@ def cmd_to_string(cmd): Used in logging """ - s = f"COMMAND: " + s = "COMMAND: " for k, v in cmd.items(): s += f"{k} [{str(v)}] " return s @@ -11,7 +11,25 @@ def cmd_to_string(cmd): def action_to_string(action): """Creates a string out of the action dictionary""" - s = f"ACTION: " + s = "ACTION: " for k, v in action.data.items(): s += f"{k} [{str(v)}] " return s + + +def is_int(i): + """Tests if i can be cast to an int.""" + try: + int(i) + except Exception: + return False + else: + return True + + +def to_bool(value): + value = str(value).lower() + if value in ["true", "t", "y", "1", "yes"]: + return True + else: + return False diff --git a/zmap.py b/zmap.py index 76beee2..a35ca5b 100644 --- a/zmap.py +++ b/zmap.py @@ -1,9 +1,3 @@ -import random - -import loader -import obj -import vec2 -import log import zmath @@ -29,7 +23,8 @@ def build_map_grid(self): the placing of blocks around the edge while still keeping the same playable space outlined in the map json. And it means we do not have to worry about accounting for edge - boundries as they cannot be reached (if the edge obj is indestructible). + boundries as they cannot be reached (if the edge obj is + indestructible). """ obj_grid = [] item_grid = [] @@ -98,7 +93,8 @@ def move_obj_from_to(self, objuuid, from_x, from_y, to_x, to_y): grid[to_x][to_y] = objuuid def get_all_obj_uuid_along_trajectory(self, x, y, angle, distance): - """Gets all object uuid's along a trajectory given a start point, angle and distance""" + """Gets all object uuid's along a trajectory given a start point, + angle and distance""" found = {} found["objects"] = [] found["items"] = [] @@ -114,7 +110,8 @@ def get_all_obj_uuid_along_trajectory(self, x, y, angle, distance): # If the obj or item grid cell is not None # create a ping and save it. for cell in cells: - if 0 <= cell[0] < self.get_data("width") and 0 <= cell[1] < self.get_data( + if 0 <= cell[0] < self.get_data("width") and \ + 0 <= cell[1] < self.get_data( "height" ): if obj_grid[cell[0]][cell[1]] is not None: @@ -131,7 +128,9 @@ def get_all_obj_uuid_along_trajectory(self, x, y, angle, distance): ping = {} ping["x"] = cell[0] ping["y"] = cell[1] - ping["distance"] = zmath.distance(x, y, cell[0], cell[1]) + ping["distance"] = zmath.distance( + x, y, cell[0], cell[1] + ) ping["uuid"] = i ping["type"] = "item" found["items"].append(ping)