From 8e3a3767208fb94017e34b0749da1cf9dd264376 Mon Sep 17 00:00:00 2001 From: Jakub Jucha Date: Sat, 10 Jan 2026 22:04:45 +0100 Subject: [PATCH 1/6] add simulation ui --- src/run.py | 10 +++++++- src/ui.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/ui.py diff --git a/src/run.py b/src/run.py index 6774300..0ad05c3 100644 --- a/src/run.py +++ b/src/run.py @@ -3,7 +3,7 @@ import pygame_gui import config from environment import Environment - +from ui import UI pygame.init() pygame.display.set_caption("Simulation") @@ -12,6 +12,13 @@ clock = pygame.time.Clock() manager = pygame_gui.UIManager((config.WINDOW_WIDTH, config.WINDOW_HEIGHT)) +env_ui = UI( + manager=manager, + x=config.PANEL_WIDTH + 2*config.PANEL_X, + y=0, + width=config.WINDOW_WIDTH - (config.PANEL_WIDTH + 2*config.PANEL_X), + height=config.WINDOW_HEIGHT +) env = Environment( grid_width=config.GRID_WIDTH, @@ -30,6 +37,7 @@ running = False manager.process_events(event) + env_ui.process_events(event, env) manager.update(dt) diff --git a/src/ui.py b/src/ui.py new file mode 100644 index 0000000..dbd0e54 --- /dev/null +++ b/src/ui.py @@ -0,0 +1,68 @@ +import pygame +import pygame_gui +from environment import Environment + +# TODO spawn agent at mouse +# TODO spawn food source at mouse +# TODO set map tile at mouse +# TODO reset simulation button +# TODO pause simulation button +# TODO log space + +class UI: + def __init__(self, manager: pygame_gui.UIManager, x: int, y: int, width: int, height: int): + self.manager = manager + + margin = 10 + btn_height = 40 + self.panel = pygame_gui.elements.UIPanel( + relative_rect=pygame.Rect(x, y, width, height), + manager=self.manager, + margins={ + 'top': margin, + 'right': margin, + 'bottom': margin, + 'left': margin + } + ) + + self.sim_ctrl_container = pygame_gui.core.UIContainer( + relative_rect=pygame.Rect(0, -btn_height, self.panel.get_abs_rect().width - 2 * margin, btn_height), + manager=manager, + container=self.panel, + anchors={ + 'bottom': 'bottom' + } + ) + + self.reset_btn = pygame_gui.elements.UIButton( + relative_rect=pygame.Rect(0, 0, (self.sim_ctrl_container.get_abs_rect().width - 2 * margin) / 3, btn_height), + text='Reset', + manager=self.manager, + container=self.sim_ctrl_container + ) + + self.pause_btn = pygame_gui.elements.UIButton( + relative_rect=pygame.Rect(margin, 0, (self.sim_ctrl_container.get_abs_rect().width - 2 * margin) / 3, btn_height), + text='Pause', + manager=self.manager, + container=self.sim_ctrl_container, + anchors={ + 'left': 'left', + 'left_target': self.reset_btn + } + ) + + self.resume_btn = pygame_gui.elements.UIButton( + relative_rect=pygame.Rect(margin, 0, (self.sim_ctrl_container.get_abs_rect().width - 2 * margin) / 3, btn_height), + text='Resume', + manager=self.manager, + container=self.sim_ctrl_container, + anchors={ + 'left': 'left', + 'left_target': self.pause_btn + } + ) + + def process_events(self, event: pygame.Event, env: Environment): + pass \ No newline at end of file From 78c8bfe7bc5700ddc91e0ef9d5b0b9377263540e Mon Sep 17 00:00:00 2001 From: Jakub Jucha Date: Sat, 10 Jan 2026 22:06:05 +0100 Subject: [PATCH 2/6] add ui event processing --- src/environment.py | 12 +++++++++++- src/ui.py | 10 +++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/environment.py b/src/environment.py index 52c52d7..e611afc 100644 --- a/src/environment.py +++ b/src/environment.py @@ -21,6 +21,7 @@ class Environment: def __init__(self, grid_width: int, grid_height: int, pixel_width: int, pixel_height: int): self.tick_counter = 0 self.running = True + self.pause_sim = False self.grid_width = grid_width self.grid_height = grid_height @@ -126,6 +127,10 @@ def _spawn_initial_agents(self): def _simulation_loop(self): while self.running: + time.sleep(0.01) + + if self.pause_sim: + continue with self.data_lock: self.tick_counter += 1 @@ -176,7 +181,6 @@ def _simulation_loop(self): agent.update(speed_modifier) self._feed_agent(agent) - time.sleep(0.01) def set_grid_cell(self, x: int, y: int, value: int): if 0 <= x < self.grid_width and 0 <= y < self.grid_height: @@ -225,6 +229,12 @@ def shutdown(self): if self.simulation_thread.is_alive(): self.simulation_thread.join(timeout=1.0) + def pause(self): + self.pause_sim = True + + def resume(self): + self.pause_sim = False + def get_agents(self): return self.agents diff --git a/src/ui.py b/src/ui.py index dbd0e54..6d44001 100644 --- a/src/ui.py +++ b/src/ui.py @@ -65,4 +65,12 @@ def __init__(self, manager: pygame_gui.UIManager, x: int, y: int, width: int, he ) def process_events(self, event: pygame.Event, env: Environment): - pass \ No newline at end of file + if event.type == pygame_gui.UI_BUTTON_PRESSED: + match event.ui_element: + case self.resume_btn: + env.resume() + case self.pause_btn: + env.pause() + case self.reset_btn: + # TODO reset simulation + pass \ No newline at end of file From ae22efdd6e242aab74df5dd7d1e3589d726f5bd6 Mon Sep 17 00:00:00 2001 From: Jakub Jucha Date: Sun, 11 Jan 2026 17:01:01 +0100 Subject: [PATCH 3/6] add spawn and area setting ui (without implementation) --- src/ui.py | 141 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 123 insertions(+), 18 deletions(-) diff --git a/src/ui.py b/src/ui.py index 6d44001..7b0edc3 100644 --- a/src/ui.py +++ b/src/ui.py @@ -1,13 +1,7 @@ import pygame import pygame_gui from environment import Environment - -# TODO spawn agent at mouse -# TODO spawn food source at mouse -# TODO set map tile at mouse -# TODO reset simulation button -# TODO pause simulation button -# TODO log space +from area import Area class UI: def __init__(self, manager: pygame_gui.UIManager, x: int, y: int, width: int, height: int): @@ -26,6 +20,91 @@ def __init__(self, manager: pygame_gui.UIManager, x: int, y: int, width: int, he } ) + # spawn ui section + self.spawn_ctrl_container = pygame_gui.elements.UIAutoResizingContainer( + relative_rect=pygame.Rect(0, 0, self.panel.get_abs_rect().width - 2 * margin, 0), + manager=self.manager, + container=self.panel, + resize_top=False, + resize_right=False, + resize_bottom=True, + resize_left=False + ) + + self.spawn_label = pygame_gui.elements.UILabel( + relative_rect=pygame.Rect(0, 0, -1, -1), + text='Spawn', + manager=self.manager, + container=self.spawn_ctrl_container, + anchors={ + 'centerx': 'centerx' + } + ) + + self.spawn_agent_btn = pygame_gui.elements.UIButton( + relative_rect=pygame.Rect(0, margin, (self.spawn_ctrl_container.get_abs_rect().width - margin) / 2, btn_height), + text='Agent', + manager=self.manager, + container=self.spawn_ctrl_container, + anchors={ + 'top_target': self.spawn_label + } + ) + + self.spawn_food_source_btn = pygame_gui.elements.UIButton( + relative_rect=pygame.Rect(margin, margin, (self.spawn_ctrl_container.get_abs_rect().width - margin) / 2, btn_height), + text='Food source', + manager=self.manager, + container=self.spawn_ctrl_container, + anchors={ + 'top_target': self.spawn_label, + 'left_target': self.spawn_agent_btn + } + ) + + # set area ui section + self.area_ctrl_container = pygame_gui.elements.UIAutoResizingContainer( + relative_rect=pygame.Rect(0, margin, self.panel.get_abs_rect().width - 2 * margin, 200), + manager=self.manager, + container=self.panel, + anchors={ + 'top_target': self.spawn_ctrl_container + } + ) + + self.area_label = pygame_gui.elements.UILabel( + relative_rect=pygame.Rect(0, 0, -1, -1), + text='Area', + manager=self.manager, + container=self.area_ctrl_container, + anchors={ + 'centerx': 'centerx' + } + ) + + self.area_btn = pygame_gui.elements.UIButton( + relative_rect=pygame.Rect(0, margin, (self.spawn_ctrl_container.get_abs_rect().width - margin) / 3, btn_height), + text='Set Area', + manager=self.manager, + container=self.area_ctrl_container, + anchors={ + 'top_target': self.area_label + } + ) + + self.area_list = pygame_gui.elements.UIDropDownMenu( + relative_rect=pygame.Rect(margin, margin, (self.spawn_ctrl_container.get_abs_rect().width - margin) / 3 * 2, btn_height), + options_list=[area.display_name for area in list(Area)], + starting_option=list(Area)[0].display_name, + manager=self.manager, + container=self.area_ctrl_container, + anchors={ + 'top_target': self.area_label, + 'left_target': self.area_btn + } + ) + + # sim control ui section self.sim_ctrl_container = pygame_gui.core.UIContainer( relative_rect=pygame.Rect(0, -btn_height, self.panel.get_abs_rect().width - 2 * margin, btn_height), manager=manager, @@ -35,42 +114,68 @@ def __init__(self, manager: pygame_gui.UIManager, x: int, y: int, width: int, he } ) - self.reset_btn = pygame_gui.elements.UIButton( + self.sim_reset_btn = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(0, 0, (self.sim_ctrl_container.get_abs_rect().width - 2 * margin) / 3, btn_height), text='Reset', manager=self.manager, container=self.sim_ctrl_container ) - self.pause_btn = pygame_gui.elements.UIButton( + self.sim_pause_btn = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(margin, 0, (self.sim_ctrl_container.get_abs_rect().width - 2 * margin) / 3, btn_height), text='Pause', manager=self.manager, container=self.sim_ctrl_container, anchors={ - 'left': 'left', - 'left_target': self.reset_btn + 'left_target': self.sim_reset_btn } ) - self.resume_btn = pygame_gui.elements.UIButton( + self.sim_resume_btn = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(margin, 0, (self.sim_ctrl_container.get_abs_rect().width - 2 * margin) / 3, btn_height), text='Resume', manager=self.manager, container=self.sim_ctrl_container, anchors={ - 'left': 'left', - 'left_target': self.pause_btn + 'left_target': self.sim_pause_btn + } + ) + + # current action label + self.action_container = pygame_gui.elements.UIAutoResizingContainer( + relative_rect=pygame.Rect(0, -btn_height, self.panel.get_abs_rect().width - 2 * margin, 0), + manager=self.manager, + container=self.panel, + anchors={ + 'bottom': 'bottom', + 'bottom_target': self.sim_ctrl_container + } + ) + + self.action_label = pygame_gui.elements.UILabel( + relative_rect=pygame.Rect(0, 0, -1, -1), + text='Current action:', + manager=self.manager, + container=self.action_container, + ) + + self.action_value = pygame_gui.elements.UILabel( + relative_rect=pygame.Rect(margin, 0, -1, -1), + text='some action', + manager=self.manager, + container=self.action_container, + anchors={ + 'left_target': self.action_label } ) def process_events(self, event: pygame.Event, env: Environment): if event.type == pygame_gui.UI_BUTTON_PRESSED: match event.ui_element: - case self.resume_btn: + case self.sim_resume_btn: env.resume() - case self.pause_btn: + case self.sim_pause_btn: env.pause() - case self.reset_btn: + case self.sim_reset_btn: # TODO reset simulation - pass \ No newline at end of file + pass From c91136f602d13f46f12cb843a70f5571655d0a02 Mon Sep 17 00:00:00 2001 From: Kamil Krawiec Date: Sun, 11 Jan 2026 17:24:41 +0100 Subject: [PATCH 4/6] Added GUI for user to have influence on environment. User can now manually add food source and change areas --- config.yaml | 2 +- src/environment.py | 42 ++++++++++++++++++++++++- src/run.py | 77 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 118 insertions(+), 3 deletions(-) diff --git a/config.yaml b/config.yaml index ff7b957..407e53b 100644 --- a/config.yaml +++ b/config.yaml @@ -4,7 +4,7 @@ window: panel: width: 700 height: 700 - x: 50 + x: 100 y: 50 grid: cell_size: 25 \ No newline at end of file diff --git a/src/environment.py b/src/environment.py index 52c52d7..6ba28ea 100644 --- a/src/environment.py +++ b/src/environment.py @@ -56,6 +56,46 @@ def _create_empty_grid(self): [0 for _ in range(self.grid_width)] for _ in range(self.grid_height) ] + def _cleanup_at(self, x: int, y: int): + """Deletes food source and all food it has generated at location (x, y).""" + with self.data_lock: + for fs in self.food_sources: + if fs.x == x and fs.y == y: + fs.destroy() + + self.food_sources = [fs for fs in self.food_sources if not fs.is_destroyed] + + self.food_items = [f for f in self.food_items if not (f.x == x and f.y == y)] + + def add_manual_food_source(self, grid_x: int, grid_y: int, source_type: str): + mapping = { + "Grass": (SimpleGrassPatch, Area.PLAINS), + "Berry": (BerryBush, Area.BERRY_CORNER), + "Fruit": (FertileFruitTree, Area.FERTILE_VALLEY), + "Cactus": (CactusPads, Area.DESERT) + } + + if source_type not in mapping: + return + cls, target_area = mapping[source_type] + + self._cleanup_at(grid_x, grid_y) + + with self.data_lock: + if 0 <= grid_x < self.grid_width and 0 <= grid_y < self.grid_height: + self.terrain[grid_y][grid_x] = target_area + new_source = cls(position=(grid_x, grid_y), + area=target_area, + env_area_counters=self.area_food_sources) + self.food_sources.append(new_source) + self.area_food_sources[target_area] += 1 + + def change_area_at(self, grid_x: int, grid_y: int, new_area: Area): + self._cleanup_at(grid_x, grid_y) + + with self.data_lock: + if 0 <= grid_x < self.grid_width and 0 <= grid_y < self.grid_height: + self.terrain[grid_y][grid_x] = new_area def is_food_source_at(self, x: int, y: int) -> bool: """Returns if food_source at location""" return any(fs.x == x and fs.y == y and not fs.is_destroyed for fs in self.food_sources) @@ -227,7 +267,7 @@ def shutdown(self): def get_agents(self): return self.agents - + #This function should deal with creating new agents def create_agent(self, agent : Agent): pass diff --git a/src/run.py b/src/run.py index 6774300..9788984 100644 --- a/src/run.py +++ b/src/run.py @@ -1,8 +1,10 @@ """Main application entry point for the simulation.""" import pygame import pygame_gui +from pygame_gui.elements import UIButton, UIPanel import config from environment import Environment +from area import Area pygame.init() @@ -10,9 +12,51 @@ screen = pygame.display.set_mode((config.WINDOW_WIDTH, config.WINDOW_HEIGHT)) clock = pygame.time.Clock() - manager = pygame_gui.UIManager((config.WINDOW_WIDTH, config.WINDOW_HEIGHT)) +FOOD_COLORS = { + "Grass": (100, 200, 100), + "Berry": (150, 70, 160), + "Fruit": (125, 200, 50), + "Cactus": (190, 165, 50) +} +HERBIVORE_DOT = (0, 200, 0) + +sidebar_width = config.PANEL_X +ui_panel = UIPanel(relative_rect=pygame.Rect(0, 0, sidebar_width, config.WINDOW_HEIGHT), + manager=manager, starting_height=1) + +button_pairs = [ + ("Grass", Area.PLAINS), + ("Berry", Area.BERRY_CORNER), + ("Fruit", Area.FERTILE_VALLEY), + ("Cactus", Area.DESERT) +] + +ui_elements = [] +y_offset = 10 +btn_w = (sidebar_width - 30) // 2 +btn_h = (sidebar_width - 30) // 2 + +for food_str, area_obj in button_pairs: + pair_color = area_obj.color + + btn_food = UIButton(relative_rect=pygame.Rect(10, y_offset, btn_w, btn_h), + text="", manager=manager, container=ui_panel) + btn_food.colours['normal_bg'] = pygame.Color(pair_color) + btn_food.rebuild() + ui_elements.append({"btn": btn_food, "value": food_str}) + + btn_area = UIButton(relative_rect=pygame.Rect(10 + btn_w + 10, y_offset, btn_w, btn_h), + text="", manager=manager, container=ui_panel) + btn_area.colours['normal_bg'] = pygame.Color(pair_color) + btn_area.rebuild() + ui_elements.append({"btn": btn_area, "value": area_obj}) + + y_offset += btn_h + 10 + +current_brush = None + env = Environment( grid_width=config.GRID_WIDTH, grid_height=config.GRID_HEIGHT, @@ -23,12 +67,31 @@ running = True while running: dt = clock.tick(60) / 1000.0 + mouse_pos = pygame.mouse.get_pos() for event in pygame.event.get(): if event.type == pygame.QUIT: env.shutdown() running = False + if event.type == pygame_gui.UI_BUTTON_PRESSED: + for item in ui_elements: + if event.ui_element == item["btn"]: + current_brush = item["value"] + + if event.type == pygame.MOUSEBUTTONDOWN: + sim_rect = pygame.Rect(config.PANEL_X, + config.PANEL_Y, + config.PANEL_WIDTH, + config.PANEL_HEIGHT) + if sim_rect.collidepoint(mouse_pos): + grid_x = (mouse_pos[0] - config.PANEL_X) // config.CELL_SIZE + grid_y = (mouse_pos[1] - config.PANEL_Y) // config.CELL_SIZE + if current_brush: + if isinstance(current_brush, Area): + env.change_area_at(grid_x, grid_y, current_brush) + else: + env.add_manual_food_source(grid_x, grid_y, current_brush) manager.process_events(event) manager.update(dt) @@ -43,6 +106,18 @@ ) manager.draw_ui(screen) + + for item in ui_elements: + btn_rect = item["btn"].get_abs_rect() + + if isinstance(item["value"], str): + dot_pos = btn_rect.center + pygame.draw.circle(screen, HERBIVORE_DOT, dot_pos, 7) + pygame.draw.circle(screen, (0, 0, 0), dot_pos, 7, 1) + + if item["value"] == current_brush: + pygame.draw.rect(screen, (255, 255, 255), btn_rect, 3) + pygame.display.flip() pygame.quit() From 8e1709fd0d7aab7c1e26f99044dfdad8e87a4bfa Mon Sep 17 00:00:00 2001 From: Jakub Jucha Date: Sun, 11 Jan 2026 21:20:23 +0100 Subject: [PATCH 5/6] add area food source mapping --- src/area.py | 8 ++++++++ src/environment.py | 15 ++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/area.py b/src/area.py index 93d09e2..5daf8c3 100644 --- a/src/area.py +++ b/src/area.py @@ -1,4 +1,5 @@ from enum import Enum +import food class Area(Enum): PLAINS = {"id": 1, @@ -39,3 +40,10 @@ def __init__(self, config): self.expansion_chance = config["expansion_chance"] self.color = config["color"] self.max_food_sources = config["max_food_sources"] + +AREA_FOOD_SOURCE_MAPPING = { + Area.PLAINS: food.SimpleGrassPatch, + Area.FERTILE_VALLEY: food.FertileFruitTree, + Area.DESERT: food.CactusPads, + Area.BERRY_CORNER: food.BerryBush +} \ No newline at end of file diff --git a/src/environment.py b/src/environment.py index 997b863..7220f1d 100644 --- a/src/environment.py +++ b/src/environment.py @@ -5,10 +5,11 @@ import random from typing import List, Tuple import pygame -from food import SimpleGrassPatch, Food, BerryBush, FertileFruitTree, CactusPads +from food import FoodSource, SimpleGrassPatch, Food, BerryBush, FertileFruitTree, CactusPads from agent import Agent from area import Area from terrain import generate_terrain +from typing import Type class Environment: """ @@ -68,17 +69,17 @@ def _cleanup_at(self, x: int, y: int): self.food_items = [f for f in self.food_items if not (f.x == x and f.y == y)] - def add_manual_food_source(self, grid_x: int, grid_y: int, source_type: str): + def add_manual_food_source(self, grid_x: int, grid_y: int, source_type: Type[FoodSource]): mapping = { - "Grass": (SimpleGrassPatch, Area.PLAINS), - "Berry": (BerryBush, Area.BERRY_CORNER), - "Fruit": (FertileFruitTree, Area.FERTILE_VALLEY), - "Cactus": (CactusPads, Area.DESERT) + SimpleGrassPatch: Area.PLAINS, + BerryBush: Area.BERRY_CORNER, + FertileFruitTree: Area.FERTILE_VALLEY, + CactusPads: Area.DESERT } if source_type not in mapping: return - cls, target_area = mapping[source_type] + cls, target_area = source_type, mapping[source_type] self._cleanup_at(grid_x, grid_y) From 0ee9362da73a672846054950285ac4bf0a6bd9ab Mon Sep 17 00:00:00 2001 From: Jakub Jucha Date: Sun, 11 Jan 2026 21:22:25 +0100 Subject: [PATCH 6/6] merge setting area and spawning food source ui --- src/run.py | 71 +++---------------------------- src/ui.py | 123 ++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 90 insertions(+), 104 deletions(-) diff --git a/src/run.py b/src/run.py index 0af5010..8ad05f1 100644 --- a/src/run.py +++ b/src/run.py @@ -5,7 +5,7 @@ import config from environment import Environment from area import Area - +from food import FoodSource from ui import UI pygame.init() @@ -22,49 +22,6 @@ height=config.WINDOW_HEIGHT ) -FOOD_COLORS = { - "Grass": (100, 200, 100), - "Berry": (150, 70, 160), - "Fruit": (125, 200, 50), - "Cactus": (190, 165, 50) -} -HERBIVORE_DOT = (0, 200, 0) - -sidebar_width = config.PANEL_X -ui_panel = UIPanel(relative_rect=pygame.Rect(0, 0, sidebar_width, config.WINDOW_HEIGHT), - manager=manager, starting_height=1) - -button_pairs = [ - ("Grass", Area.PLAINS), - ("Berry", Area.BERRY_CORNER), - ("Fruit", Area.FERTILE_VALLEY), - ("Cactus", Area.DESERT) -] - -ui_elements = [] -y_offset = 10 -btn_w = (sidebar_width - 30) // 2 -btn_h = (sidebar_width - 30) // 2 - -for food_str, area_obj in button_pairs: - pair_color = area_obj.color - - btn_food = UIButton(relative_rect=pygame.Rect(10, y_offset, btn_w, btn_h), - text="", manager=manager, container=ui_panel) - btn_food.colours['normal_bg'] = pygame.Color(pair_color) - btn_food.rebuild() - ui_elements.append({"btn": btn_food, "value": food_str}) - - btn_area = UIButton(relative_rect=pygame.Rect(10 + btn_w + 10, y_offset, btn_w, btn_h), - text="", manager=manager, container=ui_panel) - btn_area.colours['normal_bg'] = pygame.Color(pair_color) - btn_area.rebuild() - ui_elements.append({"btn": btn_area, "value": area_obj}) - - y_offset += btn_h + 10 - -current_brush = None - env = Environment( grid_width=config.GRID_WIDTH, grid_height=config.GRID_HEIGHT, @@ -82,11 +39,6 @@ env.shutdown() running = False - if event.type == pygame_gui.UI_BUTTON_PRESSED: - for item in ui_elements: - if event.ui_element == item["btn"]: - current_brush = item["value"] - if event.type == pygame.MOUSEBUTTONDOWN: sim_rect = pygame.Rect(config.PANEL_X, config.PANEL_Y, @@ -95,11 +47,11 @@ if sim_rect.collidepoint(mouse_pos): grid_x = (mouse_pos[0] - config.PANEL_X) // config.CELL_SIZE grid_y = (mouse_pos[1] - config.PANEL_Y) // config.CELL_SIZE - if current_brush: - if isinstance(current_brush, Area): - env.change_area_at(grid_x, grid_y, current_brush) - else: - env.add_manual_food_source(grid_x, grid_y, current_brush) + if env_ui.current_brush: + if isinstance(env_ui.current_brush, Area): + env.change_area_at(grid_x, grid_y, env_ui.current_brush) + elif isinstance(env_ui.current_brush, type(FoodSource)): + env.add_manual_food_source(grid_x, grid_y, env_ui.current_brush) manager.process_events(event) env_ui.process_events(event, env) @@ -116,17 +68,6 @@ manager.draw_ui(screen) - for item in ui_elements: - btn_rect = item["btn"].get_abs_rect() - - if isinstance(item["value"], str): - dot_pos = btn_rect.center - pygame.draw.circle(screen, HERBIVORE_DOT, dot_pos, 7) - pygame.draw.circle(screen, (0, 0, 0), dot_pos, 7, 1) - - if item["value"] == current_brush: - pygame.draw.rect(screen, (255, 255, 255), btn_rect, 3) - pygame.display.flip() pygame.quit() diff --git a/src/ui.py b/src/ui.py index 7b0edc3..453cfad 100644 --- a/src/ui.py +++ b/src/ui.py @@ -1,10 +1,12 @@ import pygame import pygame_gui from environment import Environment -from area import Area +from area import Area, AREA_FOOD_SOURCE_MAPPING +from agent import Agent class UI: def __init__(self, manager: pygame_gui.UIManager, x: int, y: int, width: int, height: int): + self.current_brush = None self.manager = manager margin = 10 @@ -24,11 +26,7 @@ def __init__(self, manager: pygame_gui.UIManager, x: int, y: int, width: int, he self.spawn_ctrl_container = pygame_gui.elements.UIAutoResizingContainer( relative_rect=pygame.Rect(0, 0, self.panel.get_abs_rect().width - 2 * margin, 0), manager=self.manager, - container=self.panel, - resize_top=False, - resize_right=False, - resize_bottom=True, - resize_left=False + container=self.panel ) self.spawn_label = pygame_gui.elements.UILabel( @@ -42,7 +40,7 @@ def __init__(self, manager: pygame_gui.UIManager, x: int, y: int, width: int, he ) self.spawn_agent_btn = pygame_gui.elements.UIButton( - relative_rect=pygame.Rect(0, margin, (self.spawn_ctrl_container.get_abs_rect().width - margin) / 2, btn_height), + relative_rect=pygame.Rect(0, margin, self.spawn_ctrl_container.get_abs_rect().width, btn_height), text='Agent', manager=self.manager, container=self.spawn_ctrl_container, @@ -50,18 +48,40 @@ def __init__(self, manager: pygame_gui.UIManager, x: int, y: int, width: int, he 'top_target': self.spawn_label } ) - - self.spawn_food_source_btn = pygame_gui.elements.UIButton( - relative_rect=pygame.Rect(margin, margin, (self.spawn_ctrl_container.get_abs_rect().width - margin) / 2, btn_height), - text='Food source', + + self.spawn_food_source_container = pygame_gui.elements.UIAutoResizingContainer( + relative_rect=pygame.Rect(0, 0, self.spawn_ctrl_container.get_abs_rect().width, 0), manager=self.manager, container=self.spawn_ctrl_container, anchors={ - 'top_target': self.spawn_label, - 'left_target': self.spawn_agent_btn + 'top_target': self.spawn_agent_btn } ) + self.spawn_food_source_btns = dict() + y_offset = margin + for area in list(Area): + btn = pygame_gui.elements.UIButton( + relative_rect=pygame.Rect(0, y_offset, btn_height, btn_height), + text='', + manager=self.manager, + container=self.spawn_food_source_container, + + ) + btn.colours['normal_bg'] = pygame.Color(area.color) + btn.rebuild() + label = pygame_gui.elements.UILabel( + relative_rect=pygame.Rect(margin, y_offset, -1, btn_height), + text=AREA_FOOD_SOURCE_MAPPING[area].__name__, + manager=self.manager, + container=self.spawn_food_source_container, + anchors={ + 'left_target': btn + } + ) + self.spawn_food_source_btns[btn] = AREA_FOOD_SOURCE_MAPPING[area] + y_offset += margin + btn_height + # set area ui section self.area_ctrl_container = pygame_gui.elements.UIAutoResizingContainer( relative_rect=pygame.Rect(0, margin, self.panel.get_abs_rect().width - 2 * margin, 200), @@ -82,27 +102,39 @@ def __init__(self, manager: pygame_gui.UIManager, x: int, y: int, width: int, he } ) - self.area_btn = pygame_gui.elements.UIButton( - relative_rect=pygame.Rect(0, margin, (self.spawn_ctrl_container.get_abs_rect().width - margin) / 3, btn_height), - text='Set Area', + self.set_area_btns_container = pygame_gui.elements.UIAutoResizingContainer( + relative_rect=pygame.Rect(0, 0, self.area_ctrl_container.get_abs_rect().width, 0), manager=self.manager, container=self.area_ctrl_container, anchors={ 'top_target': self.area_label } ) - - self.area_list = pygame_gui.elements.UIDropDownMenu( - relative_rect=pygame.Rect(margin, margin, (self.spawn_ctrl_container.get_abs_rect().width - margin) / 3 * 2, btn_height), - options_list=[area.display_name for area in list(Area)], - starting_option=list(Area)[0].display_name, - manager=self.manager, - container=self.area_ctrl_container, - anchors={ - 'top_target': self.area_label, - 'left_target': self.area_btn - } - ) + + self.set_area_btns = dict() + y_offset = margin + for area in list(Area): + btn = pygame_gui.elements.UIButton( + relative_rect=pygame.Rect(0, y_offset, btn_height, btn_height), + text='', + manager=self.manager, + container=self.set_area_btns_container, + + ) + btn.colours['normal_bg'] = pygame.Color(area.color) + btn.rebuild() + label = pygame_gui.elements.UILabel( + relative_rect=pygame.Rect(margin, y_offset, -1, btn_height), + text=area.display_name, + manager=self.manager, + container=self.set_area_btns_container, + anchors={ + 'left_target': btn + } + ) + self.set_area_btns[btn] = area + y_offset += margin + btn_height + # sim control ui section self.sim_ctrl_container = pygame_gui.core.UIContainer( @@ -154,28 +186,41 @@ def __init__(self, manager: pygame_gui.UIManager, x: int, y: int, width: int, he self.action_label = pygame_gui.elements.UILabel( relative_rect=pygame.Rect(0, 0, -1, -1), - text='Current action:', + text='Current brush:', manager=self.manager, container=self.action_container, ) self.action_value = pygame_gui.elements.UILabel( relative_rect=pygame.Rect(margin, 0, -1, -1), - text='some action', + text='', manager=self.manager, container=self.action_container, anchors={ 'left_target': self.action_label } ) - + def process_events(self, event: pygame.Event, env: Environment): if event.type == pygame_gui.UI_BUTTON_PRESSED: - match event.ui_element: - case self.sim_resume_btn: - env.resume() - case self.sim_pause_btn: - env.pause() - case self.sim_reset_btn: - # TODO reset simulation - pass + if event.ui_element == self.sim_resume_btn: + env.resume() + elif event.ui_element == self.sim_pause_btn: + env.pause() + elif event.ui_element == self.sim_reset_btn: + # TODO reset simulation + pass + elif event.ui_element == self.spawn_agent_btn: + self.change_brush(Agent, 'Agent') + elif event.ui_element in self.set_area_btns: + area = self.set_area_btns[event.ui_element] + self.change_brush(area, area.display_name) + elif event.ui_element in self.spawn_food_source_btns: + food_source = self.spawn_food_source_btns[event.ui_element] + self.change_brush(food_source, food_source.__name__) + + def change_brush(self, new_brush, brush_name: str): + self.current_brush = new_brush + self.action_value.set_text(brush_name) + +