Compare commits
	
		
			19 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b9027ab935 | |||
| b775813cbd | |||
| bce07db40e | |||
| 0d95e85d83 | |||
| 22406420c2 | |||
| 31c3244b5a | |||
| 8bb5c3edfc | |||
| 4e90ecb885 | |||
| 8bb669d6b2 | |||
| d0f01c0a48 | |||
| 15bc179410 | |||
| f74aa1f633 | |||
| dee0eaa9f9 | |||
| 7289543f6a | |||
| 8f17498b88 | |||
| f56192ab8f | |||
| 4a4f7a75c5 | |||
| 6f9e1e84f0 | |||
| 8c8d8f7925 | 
| @ -19,8 +19,8 @@ SELECTION_GRAY = (128, 128, 128, 80) | |||||||
| SELECTION_BORDER = (80, 80, 90) | SELECTION_BORDER = (80, 80, 90) | ||||||
| 
 | 
 | ||||||
| # Grid settings | # Grid settings | ||||||
| GRID_WIDTH = 30 | GRID_WIDTH = 50 | ||||||
| GRID_HEIGHT = 25 | GRID_HEIGHT = 50 | ||||||
| CELL_SIZE = 20 | CELL_SIZE = 20 | ||||||
| RENDER_BUFFER = 50 | RENDER_BUFFER = 50 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ import pygame | |||||||
| import math | import math | ||||||
| from config.constants import * | from config.constants import * | ||||||
| from world.base.brain import CellBrain | from world.base.brain import CellBrain | ||||||
|  | from world.objects import DefaultCell | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Renderer: | class Renderer: | ||||||
| @ -13,11 +14,8 @@ class Renderer: | |||||||
|         self.render_height = render_area.get_height() |         self.render_height = render_area.get_height() | ||||||
|         self.render_width = render_area.get_width() |         self.render_width = render_area.get_width() | ||||||
| 
 | 
 | ||||||
|     def clear_screen(self, main_screen=None): |     def clear_screen(self): | ||||||
|         """Clear the screen with a black background.""" |         """Clear the screen with a black background.""" | ||||||
|         if main_screen: |  | ||||||
|             main_screen.fill(BLACK) |  | ||||||
| 
 |  | ||||||
|         self.render_area.fill(BLACK) |         self.render_area.fill(BLACK) | ||||||
| 
 | 
 | ||||||
|     def draw_grid(self, camera, showing_grid=True): |     def draw_grid(self, camera, showing_grid=True): | ||||||
| @ -101,6 +99,9 @@ class Renderer: | |||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
|         for obj in world.get_objects(): |         for obj in world.get_objects(): | ||||||
|  |             if not isinstance(obj, DefaultCell): | ||||||
|  |                 continue | ||||||
|  | 
 | ||||||
|             obj_x, obj_y = obj.position.get_position() |             obj_x, obj_y = obj.position.get_position() | ||||||
|             radius = obj.interaction_radius |             radius = obj.interaction_radius | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -18,21 +18,29 @@ from ui.hud import HUD | |||||||
| class SimulationEngine: | class SimulationEngine: | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         pygame.init() |         pygame.init() | ||||||
|  |         self._init_window() | ||||||
|  |         self._init_ui() | ||||||
|  |         self._init_simulation() | ||||||
|  |         self.running = True | ||||||
| 
 | 
 | ||||||
|  |     def _init_window(self): | ||||||
|         info = pygame.display.Info() |         info = pygame.display.Info() | ||||||
|         self.window_width, self.window_height = info.current_w // 2, info.current_h // 2 |         self.window_width = int(info.current_w // 1.5) | ||||||
|         self.screen = pygame.display.set_mode((self.window_width, self.window_height), |         self.window_height = int(info.current_h // 1.5) | ||||||
|                                               pygame.RESIZABLE, vsync=1) |         self.screen = pygame.display.set_mode( | ||||||
| 
 |             (self.window_width, self.window_height), | ||||||
|         self.ui_manager = UIManager((self.window_width, self.window_height)) |             pygame.RESIZABLE, vsync=1 | ||||||
| 
 |         ) | ||||||
|         self.camera = Camera(SCREEN_WIDTH, SCREEN_HEIGHT, RENDER_BUFFER) |  | ||||||
|         self._update_simulation_view() |  | ||||||
| 
 |  | ||||||
|         # self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), vsync=1) |  | ||||||
|         pygame.display.set_caption("Dynamic Abstraction System Testing") |         pygame.display.set_caption("Dynamic Abstraction System Testing") | ||||||
|         self.clock = pygame.time.Clock() |         self.clock = pygame.time.Clock() | ||||||
| 
 | 
 | ||||||
|  |     def _init_ui(self): | ||||||
|  |         self.ui_manager = UIManager((self.window_width, self.window_height)) | ||||||
|  |         self.hud = HUD(self.ui_manager, self.window_width, self.window_height) | ||||||
|  |         self.hud.update_layout(self.window_width, self.window_height) | ||||||
|  |         self._update_simulation_view() | ||||||
|  | 
 | ||||||
|  |     def _init_simulation(self): | ||||||
|         self.last_tick_time = time.perf_counter() |         self.last_tick_time = time.perf_counter() | ||||||
|         self.last_tps_time = time.perf_counter() |         self.last_tps_time = time.perf_counter() | ||||||
|         self.tick_counter = 0 |         self.tick_counter = 0 | ||||||
| @ -42,21 +50,17 @@ class SimulationEngine: | |||||||
|         self.world = self._setup_world() |         self.world = self._setup_world() | ||||||
|         self.input_handler = InputHandler(self.camera, self.world, self.sim_view_rect) |         self.input_handler = InputHandler(self.camera, self.world, self.sim_view_rect) | ||||||
|         self.renderer = Renderer(self.sim_view) |         self.renderer = Renderer(self.sim_view) | ||||||
|         self.hud = HUD(self.ui_manager, self.window_width, self.window_height) |  | ||||||
|         self.hud.update_layout(self.window_width, self.window_height) |  | ||||||
| 
 |  | ||||||
|         self.running = True |  | ||||||
| 
 | 
 | ||||||
|     def _update_simulation_view(self): |     def _update_simulation_view(self): | ||||||
|         self.sim_view_width = int(self.window_width * 0.75) |         viewport_rect = self.hud.get_viewport_rect() | ||||||
|         self.sim_view_height = int(self.window_height * 0.75) |         self.sim_view_width = viewport_rect.width | ||||||
|  |         self.sim_view_height = viewport_rect.height | ||||||
|         self.sim_view = pygame.Surface((self.sim_view_width, self.sim_view_height)) |         self.sim_view = pygame.Surface((self.sim_view_width, self.sim_view_height)) | ||||||
|         self.sim_view_rect = self.sim_view.get_rect(center=(self.window_width // 2, self.window_height // 2)) |         self.sim_view_rect = self.sim_view.get_rect(topleft=(viewport_rect.left, viewport_rect.top)) | ||||||
| 
 | 
 | ||||||
|         self.ui_manager.set_window_resolution((self.window_width, self.window_height)) |         self.ui_manager.set_window_resolution((self.window_width, self.window_height)) | ||||||
|         self.renderer = Renderer(self.sim_view) |         self.renderer = Renderer(self.sim_view) | ||||||
| 
 | 
 | ||||||
|         # Update camera to match new sim_view size |  | ||||||
|         if hasattr(self, 'camera'): |         if hasattr(self, 'camera'): | ||||||
|             self.camera.screen_width = self.sim_view_width |             self.camera.screen_width = self.sim_view_width | ||||||
|             self.camera.screen_height = self.sim_view_height |             self.camera.screen_height = self.sim_view_height | ||||||
| @ -64,6 +68,8 @@ class SimulationEngine: | |||||||
|         if hasattr(self, 'input_handler'): |         if hasattr(self, 'input_handler'): | ||||||
|             self.input_handler.update_sim_view_rect(self.sim_view_rect) |             self.input_handler.update_sim_view_rect(self.sim_view_rect) | ||||||
| 
 | 
 | ||||||
|  |         if not hasattr(self, 'camera'): | ||||||
|  |             self.camera = Camera(self.sim_view_width, self.sim_view_height, RENDER_BUFFER) | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def _setup_world(): |     def _setup_world(): | ||||||
| @ -75,23 +81,32 @@ class SimulationEngine: | |||||||
| 
 | 
 | ||||||
|         if FOOD_SPAWNING: |         if FOOD_SPAWNING: | ||||||
|             for _ in range(FOOD_OBJECTS_COUNT): |             for _ in range(FOOD_OBJECTS_COUNT): | ||||||
|                 x = random.randint(-half_width, half_width) |                 x = random.randint(-half_width // 2, half_width // 2) | ||||||
|                 y = random.randint(-half_height, half_height) |                 y = random.randint(-half_height // 2, half_height // 2) | ||||||
|                 world.add_object(FoodObject(Position(x=x, y=y))) |                 world.add_object(FoodObject(Position(x=x, y=y))) | ||||||
| 
 | 
 | ||||||
|         for _ in range(300): |         for _ in range(350): | ||||||
|             new_cell = DefaultCell(Position(x=random.randint(-half_width, half_width), y=random.randint(-half_height, half_height)), Rotation(angle=0)) |             new_cell = DefaultCell( | ||||||
| 
 |                 Position(x=random.randint(-half_width // 2, half_width // 2), y=random.randint(-half_height // 2, half_height // 2)), | ||||||
|  |                 Rotation(angle=0) | ||||||
|  |             ) | ||||||
|             new_cell.behavioral_model = new_cell.behavioral_model.mutate(3) |             new_cell.behavioral_model = new_cell.behavioral_model.mutate(3) | ||||||
| 
 |  | ||||||
|             world.add_object(new_cell) |             world.add_object(new_cell) | ||||||
| 
 | 
 | ||||||
|         return world |         return world | ||||||
| 
 | 
 | ||||||
|  |     def _count_cells(self): | ||||||
|  |         count = 0 | ||||||
|  |         for entity in self.world.get_objects(): | ||||||
|  |             if isinstance(entity, DefaultCell): | ||||||
|  |                 count += 1 | ||||||
|  |         return count | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     def run(self): |     def run(self): | ||||||
|  |         print(self.world.current_buffer) | ||||||
|         while self.running: |         while self.running: | ||||||
|             self._handle_frame() |             self._handle_frame() | ||||||
| 
 |  | ||||||
|         pygame.quit() |         pygame.quit() | ||||||
|         sys.exit() |         sys.exit() | ||||||
| 
 | 
 | ||||||
| @ -99,53 +114,24 @@ class SimulationEngine: | |||||||
|         deltatime = self.clock.get_time() / 1000.0 |         deltatime = self.clock.get_time() / 1000.0 | ||||||
|         tick_interval = 1.0 / self.input_handler.tps |         tick_interval = 1.0 / self.input_handler.tps | ||||||
| 
 | 
 | ||||||
|         # Handle events |  | ||||||
|         events = pygame.event.get() |         events = pygame.event.get() | ||||||
|         self.running = self.input_handler.handle_events(events, self.hud.manager) |         self.running = self.input_handler.handle_events(events, self.hud.manager) | ||||||
| 
 |         self._handle_window_events(events) | ||||||
|         for event in events: |  | ||||||
|             if event.type == pygame.VIDEORESIZE: |  | ||||||
|                 self.window_width, self.window_height = event.w, event.h |  | ||||||
|                 self.screen = pygame.display.set_mode((self.window_width, self.window_height), |  | ||||||
|                                                       pygame.RESIZABLE) |  | ||||||
|                 self._update_simulation_view() |  | ||||||
|                 self.hud.update_layout(self.window_width, self.window_height) |  | ||||||
| 
 | 
 | ||||||
|         if self.input_handler.sprint_mode: |         if self.input_handler.sprint_mode: | ||||||
|             # Sprint mode: run as many ticks as possible, skip rendering |             self._handle_sprint_mode() | ||||||
|             current_time = time.perf_counter() |  | ||||||
|             while True: |  | ||||||
|                 self.input_handler.update_selected_objects() |  | ||||||
|                 self.world.tick_all() |  | ||||||
|                 self.tick_counter += 1 |  | ||||||
|                 self.total_ticks += 1 |  | ||||||
|                 # Optionally break after some time to allow event processing |  | ||||||
|                 if time.perf_counter() - current_time > 0.05:  # ~50ms per batch |  | ||||||
|                     break |  | ||||||
|             # Update TPS every second |  | ||||||
|             if time.perf_counter() - self.last_tps_time >= 1.0: |  | ||||||
|                 self.actual_tps = self.tick_counter |  | ||||||
|                 self.tick_counter = 0 |  | ||||||
|                 self.last_tps_time = time.perf_counter() |  | ||||||
|             # No rendering or camera update |  | ||||||
| 
 |  | ||||||
|             self.renderer.clear_screen() |  | ||||||
|             self.hud.render_sprint_debug(self.screen, self.actual_tps, self.total_ticks) |  | ||||||
|             pygame.display.flip() |  | ||||||
|             self.clock.tick(MAX_FPS) |  | ||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
|  |         # Only process one tick per frame if enough time has passed | ||||||
|         if not self.input_handler.is_paused: |         if not self.input_handler.is_paused: | ||||||
|             current_time = time.perf_counter() |             current_time = time.perf_counter() | ||||||
|             while current_time - self.last_tick_time >= tick_interval: |             if current_time - self.last_tick_time >= tick_interval: | ||||||
|                 self.last_tick_time += tick_interval |                 self.last_tick_time += tick_interval | ||||||
|                 self.tick_counter += 1 |                 self.tick_counter += 1 | ||||||
|                 self.total_ticks += 1 |                 self.total_ticks += 1 | ||||||
| 
 |  | ||||||
|                 self.input_handler.update_selected_objects() |                 self.input_handler.update_selected_objects() | ||||||
|                 self.world.tick_all() |                 self.world.tick_all() | ||||||
|                 self.hud.manager.update(deltatime) |                 self.hud.manager.update(deltatime) | ||||||
| 
 |  | ||||||
|             if current_time - self.last_tps_time >= 1.0: |             if current_time - self.last_tps_time >= 1.0: | ||||||
|                 self.actual_tps = self.tick_counter |                 self.actual_tps = self.tick_counter | ||||||
|                 self.tick_counter = 0 |                 self.tick_counter = 0 | ||||||
| @ -158,36 +144,87 @@ class SimulationEngine: | |||||||
|         self._update(deltatime) |         self._update(deltatime) | ||||||
|         self._render() |         self._render() | ||||||
| 
 | 
 | ||||||
|  |     def _handle_window_events(self, events): | ||||||
|  |         for event in events: | ||||||
|  |             self.hud.process_event(event) | ||||||
|  |             if event.type == pygame.VIDEORESIZE: | ||||||
|  |                 self.window_width, self.window_height = event.w, event.h | ||||||
|  |                 self.screen = pygame.display.set_mode( | ||||||
|  |                     (self.window_width, self.window_height), | ||||||
|  |                     pygame.RESIZABLE | ||||||
|  |                 ) | ||||||
|  |                 self._update_simulation_view() | ||||||
|  |                 self.hud.update_layout(self.window_width, self.window_height) | ||||||
|  | 
 | ||||||
|  |         self.hud.update_layout(self.window_width, self.window_height) | ||||||
|  |         self._update_simulation_view() | ||||||
|  | 
 | ||||||
|  |     def _handle_sprint_mode(self): | ||||||
|  |         current_time = time.perf_counter() | ||||||
|  |         while True: | ||||||
|  |             self.input_handler.update_selected_objects() | ||||||
|  |             self.world.tick_all() | ||||||
|  |             self.tick_counter += 1 | ||||||
|  |             self.total_ticks += 1 | ||||||
|  |             pygame.event.pump()  # Prevent event queue overflow | ||||||
|  |             if time.perf_counter() - current_time > 0.05: | ||||||
|  |                 break | ||||||
|  |         if time.perf_counter() - self.last_tps_time >= 1.0: | ||||||
|  |             self.actual_tps = self.tick_counter | ||||||
|  |             self.tick_counter = 0 | ||||||
|  |             self.last_tps_time = time.perf_counter() | ||||||
|  |         self.screen.fill(BLACK) | ||||||
|  |         self.renderer.clear_screen() | ||||||
|  |         cell_count = self._count_cells() | ||||||
|  |         self.hud.render_sprint_debug(self.screen, self.actual_tps, self.total_ticks, cell_count) | ||||||
|  |         pygame.display.flip() | ||||||
|  |         self.clock.tick(MAX_FPS) | ||||||
|  |         self.last_tick_time = time.perf_counter() | ||||||
|  | 
 | ||||||
|  |     def _handle_simulation_ticks(self, tick_interval, deltatime): | ||||||
|  |         current_time = time.perf_counter() | ||||||
|  |         while current_time - self.last_tick_time >= tick_interval: | ||||||
|  |             self.last_tick_time += tick_interval | ||||||
|  |             self.tick_counter += 1 | ||||||
|  |             self.total_ticks += 1 | ||||||
|  |             self.input_handler.update_selected_objects() | ||||||
|  |             self.world.tick_all() | ||||||
|  |             self.hud.manager.update(deltatime) | ||||||
|  |         if current_time - self.last_tps_time >= 1.0: | ||||||
|  |             self.actual_tps = self.tick_counter | ||||||
|  |             self.tick_counter = 0 | ||||||
|  |             self.last_tps_time += 1.0 | ||||||
|  | 
 | ||||||
|     def _update(self, deltatime): |     def _update(self, deltatime): | ||||||
|         keys = pygame.key.get_pressed() |         keys = pygame.key.get_pressed() | ||||||
|         self.input_handler.update_camera(keys, deltatime) |         self.input_handler.update_camera(keys, deltatime) | ||||||
| 
 | 
 | ||||||
|     def _render(self): |     def _render(self): | ||||||
|         self.renderer.clear_screen(self.screen) |         self.screen.fill(BLACK) | ||||||
|         self.renderer.draw_grid(self.camera, self.input_handler.show_grid) |         self.renderer.clear_screen() | ||||||
|         self.renderer.render_world(self.world, self.camera) |  | ||||||
|         self.renderer.render_interaction_radius(self.world, self.camera, self.input_handler.selected_objects, self.input_handler.show_interaction_radius) |  | ||||||
|         self.renderer.render_selection_rectangle(self.input_handler.get_selection_rect(), self.sim_view_rect) |  | ||||||
|         self.renderer.render_selected_objects_outline(self.input_handler.selected_objects, self.camera) |  | ||||||
| 
 | 
 | ||||||
|         # In core/simulation_engine.py, in _render(): |         if not self.hud.dragging_splitter: | ||||||
|         self.screen.blit(self.sim_view, (self.sim_view_rect.left, self.sim_view_rect.top)) |             self.renderer.draw_grid(self.camera, self.input_handler.show_grid) | ||||||
|  |             self.renderer.render_world(self.world, self.camera) | ||||||
|  |             self.renderer.render_interaction_radius( | ||||||
|  |                 self.world, self.camera, self.input_handler.selected_objects, self.input_handler.show_interaction_radius | ||||||
|  |             ) | ||||||
|  |             self.renderer.render_selection_rectangle(self.input_handler.get_selection_rect(), self.sim_view_rect) | ||||||
|  |             self.renderer.render_selected_objects_outline(self.input_handler.selected_objects, self.camera) | ||||||
|  |             self.screen.blit(self.sim_view, (self.sim_view_rect.left, self.sim_view_rect.top)) | ||||||
| 
 | 
 | ||||||
|         # Draw border around sim_view |         self.hud.manager.draw_ui(self.screen) | ||||||
|         border_color = (255, 255, 255)  # White |         self.hud.draw_splitters(self.screen) | ||||||
|         border_width = 3 |  | ||||||
|         pygame.draw.rect(self.screen, border_color, self.sim_view_rect, border_width) |  | ||||||
| 
 | 
 | ||||||
|         self.hud.render_mouse_position(self.screen, self.camera, self.sim_view_rect) |         # self.hud.render_mouse_position(self.screen, self.camera, self.sim_view_rect) | ||||||
|         self.hud.render_fps(self.screen, self.clock) |         self.hud.render_fps(self.screen, self.clock) | ||||||
|         self.hud.render_tps(self.screen, self.actual_tps) |         self.hud.render_tps(self.screen, self.actual_tps) | ||||||
|         self.hud.render_tick_count(self.screen, self.total_ticks) |         # self.hud.render_tick_count(self.screen, self.total_ticks) | ||||||
|         self.hud.render_selected_objects_info(self.screen, self.input_handler.selected_objects) |         # self.hud.render_selected_objects_info(self.screen, self.input_handler.selected_objects) | ||||||
|         self.hud.render_legend(self.screen, self.input_handler.show_legend) |         self.hud.render_legend(self.screen, self.input_handler.show_legend) | ||||||
|         self.hud.render_pause_indicator(self.screen, self.input_handler.is_paused) |         self.hud.render_pause_indicator(self.screen, self.input_handler.is_paused) | ||||||
| 
 |  | ||||||
|         if self.input_handler.selected_objects: |         if self.input_handler.selected_objects: | ||||||
|             self.hud.render_neural_network_visualization(self.screen, self.input_handler.selected_objects[0]) |             self.hud.render_neural_network_visualization(self.screen, self.input_handler.selected_objects[0]) | ||||||
| 
 | 
 | ||||||
|         pygame.display.flip() |         pygame.display.flip() | ||||||
|         self.clock.tick(MAX_FPS) |         self.clock.tick(MAX_FPS) | ||||||
							
								
								
									
										5
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								main.py
									
									
									
									
									
								
							| @ -1,5 +1,8 @@ | |||||||
| from core.simulation_engine import SimulationEngine | from core.simulation_engine import SimulationEngine | ||||||
| 
 | 
 | ||||||
| if __name__ == "__main__": | def main(): | ||||||
|     engine = SimulationEngine() |     engine = SimulationEngine() | ||||||
|     engine.run() |     engine.run() | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     main() | ||||||
| @ -14,5 +14,6 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [dependency-groups] | [dependency-groups] | ||||||
| dev = [ | dev = [ | ||||||
|  |     "psutil>=7.0.0", | ||||||
|     "ruff>=0.11.12", |     "ruff>=0.11.12", | ||||||
| ] | ] | ||||||
|  | |||||||
							
								
								
									
										93
									
								
								tests/benchmarking.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								tests/benchmarking.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | |||||||
|  | import time | ||||||
|  | import random | ||||||
|  | import statistics | ||||||
|  | import hashlib | ||||||
|  | import pickle | ||||||
|  | 
 | ||||||
|  | class HeadlessSimulationBenchmark: | ||||||
|  |     def __init__(self, setup_world, random_seed=42): | ||||||
|  |         """ | ||||||
|  |         :param setup_world: Callable that returns a World instance. | ||||||
|  |         :param random_seed: Seed for random number generation. | ||||||
|  |         """ | ||||||
|  |         self.setup_world = setup_world | ||||||
|  |         self.random_seed = random_seed | ||||||
|  |         self.world = None | ||||||
|  |         self.tps_history = [] | ||||||
|  |         self._running = False | ||||||
|  |         self.ticks_elapsed_time = None  # Track time for designated ticks | ||||||
|  | 
 | ||||||
|  |     def set_random_seed(self, seed): | ||||||
|  |         self.random_seed = seed | ||||||
|  |         random.seed(seed) | ||||||
|  | 
 | ||||||
|  |     def start(self, ticks=100, max_seconds=None): | ||||||
|  |         self.set_random_seed(self.random_seed) | ||||||
|  |         self.world = self.setup_world(self.random_seed) | ||||||
|  |         self.tps_history.clear() | ||||||
|  |         self._running = True | ||||||
|  | 
 | ||||||
|  |         tick_count = 0 | ||||||
|  |         start_time = time.perf_counter() | ||||||
|  |         last_time = start_time | ||||||
|  | 
 | ||||||
|  |         # For precise tick timing | ||||||
|  |         tick_timing_start = None | ||||||
|  | 
 | ||||||
|  |         if ticks is not None: | ||||||
|  |             tick_timing_start = time.perf_counter() | ||||||
|  | 
 | ||||||
|  |         while self._running and (ticks is None or tick_count < ticks): | ||||||
|  |             self.world.tick_all() | ||||||
|  |             tick_count += 1 | ||||||
|  |             now = time.perf_counter() | ||||||
|  |             elapsed = now - last_time | ||||||
|  |             if elapsed > 0: | ||||||
|  |                 self.tps_history.append(1.0 / elapsed) | ||||||
|  |             last_time = now | ||||||
|  |             if max_seconds and (now - start_time) > max_seconds: | ||||||
|  |                 break | ||||||
|  | 
 | ||||||
|  |         if ticks is not None: | ||||||
|  |             tick_timing_end = time.perf_counter() | ||||||
|  |             self.ticks_elapsed_time = tick_timing_end - tick_timing_start | ||||||
|  |         else: | ||||||
|  |             self.ticks_elapsed_time = None | ||||||
|  | 
 | ||||||
|  |         self._running = False | ||||||
|  | 
 | ||||||
|  |     def stop(self): | ||||||
|  |         self._running = False | ||||||
|  | 
 | ||||||
|  |     def get_tps_history(self): | ||||||
|  |         return self.tps_history | ||||||
|  | 
 | ||||||
|  |     def get_tps_average(self): | ||||||
|  |         return statistics.mean(self.tps_history) if self.tps_history else 0.0 | ||||||
|  | 
 | ||||||
|  |     def get_tps_stddev(self): | ||||||
|  |         return statistics.stdev(self.tps_history) if len(self.tps_history) > 1 else 0.0 | ||||||
|  | 
 | ||||||
|  |     def get_simulation_hash(self): | ||||||
|  |         # Serialize the world state and hash it for determinism checks | ||||||
|  |         state = [] | ||||||
|  |         for obj in self.world.get_objects(): | ||||||
|  |             state.append(( | ||||||
|  |                 type(obj).__name__, | ||||||
|  |                 getattr(obj, "position", None), | ||||||
|  |                 getattr(obj, "rotation", None), | ||||||
|  |                 getattr(obj, "flags", None), | ||||||
|  |                 getattr(obj, "interaction_radius", None), | ||||||
|  |                 getattr(obj, "max_visual_width", None), | ||||||
|  |             )) | ||||||
|  |         state_bytes = pickle.dumps(state) | ||||||
|  |         return hashlib.sha256(state_bytes).hexdigest() | ||||||
|  | 
 | ||||||
|  |     def get_summary(self): | ||||||
|  |         return { | ||||||
|  |             "tps_avg": self.get_tps_average(), | ||||||
|  |             "tps_stddev": self.get_tps_stddev(), | ||||||
|  |             "ticks": len(self.tps_history), | ||||||
|  |             "simulation_hash": self.get_simulation_hash(), | ||||||
|  |             "ticks_elapsed_time": self.ticks_elapsed_time, | ||||||
|  |         } | ||||||
							
								
								
									
										57
									
								
								tests/test_determinism.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								tests/test_determinism.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | |||||||
|  | import pytest | ||||||
|  | import random | ||||||
|  | 
 | ||||||
|  | from world.world import World, Position, Rotation | ||||||
|  | from world.objects import FoodObject, DefaultCell | ||||||
|  | from tests.benchmarking import HeadlessSimulationBenchmark | ||||||
|  | 
 | ||||||
|  | # Hardcoded simulation parameters (copied from config/constants.py) | ||||||
|  | CELL_SIZE = 20 | ||||||
|  | GRID_WIDTH = 30 | ||||||
|  | GRID_HEIGHT = 25 | ||||||
|  | FOOD_OBJECTS_COUNT = 500 | ||||||
|  | RANDOM_SEED = 12345 | ||||||
|  | 
 | ||||||
|  | def _setup_world(seed=RANDOM_SEED): | ||||||
|  |     world = World(CELL_SIZE, (CELL_SIZE * GRID_WIDTH, CELL_SIZE * GRID_HEIGHT)) | ||||||
|  |     random.seed(seed) | ||||||
|  | 
 | ||||||
|  |     half_width = GRID_WIDTH * CELL_SIZE // 2 | ||||||
|  |     half_height = GRID_HEIGHT * CELL_SIZE // 2 | ||||||
|  | 
 | ||||||
|  |     for _ in range(FOOD_OBJECTS_COUNT): | ||||||
|  |         x = random.randint(-half_width, half_width) | ||||||
|  |         y = random.randint(-half_height, half_height) | ||||||
|  |         world.add_object(FoodObject(Position(x=x, y=y))) | ||||||
|  | 
 | ||||||
|  |     for _ in range(300): | ||||||
|  |         new_cell = DefaultCell( | ||||||
|  |             Position(x=random.randint(-half_width, half_width), y=random.randint(-half_height, half_height)), | ||||||
|  |             Rotation(angle=0) | ||||||
|  |         ) | ||||||
|  |         new_cell.behavioral_model = new_cell.behavioral_model.mutate(3) | ||||||
|  |         world.add_object(new_cell) | ||||||
|  | 
 | ||||||
|  |     return world | ||||||
|  | 
 | ||||||
|  | def test_simulation_determinism(): | ||||||
|  |     bench1 = HeadlessSimulationBenchmark(lambda seed: _setup_world(seed), random_seed=RANDOM_SEED) | ||||||
|  |     bench2 = HeadlessSimulationBenchmark(lambda seed: _setup_world(seed), random_seed=RANDOM_SEED) | ||||||
|  | 
 | ||||||
|  |     bench1.start(ticks=100) | ||||||
|  |     bench2.start(ticks=100) | ||||||
|  | 
 | ||||||
|  |     hash1 = bench1.get_simulation_hash() | ||||||
|  |     hash2 = bench2.get_simulation_hash() | ||||||
|  | 
 | ||||||
|  |     assert hash1 == hash2, f"Simulation hashes differ: {hash1} != {hash2}" | ||||||
|  | 
 | ||||||
|  | def test_simulation_benchmark(): | ||||||
|  |     bench = HeadlessSimulationBenchmark(lambda seed: _setup_world(seed), random_seed=RANDOM_SEED+1) | ||||||
|  |     tick_count = 100 | ||||||
|  |     bench.start(ticks=tick_count) | ||||||
|  |     summary = bench.get_summary() | ||||||
|  |     print(f"{tick_count} ticks took {summary.get('ticks_elapsed_time', 0):.4f} seconds, TPS avg: {summary['tps_avg']:.2f}, stddev: {summary['tps_stddev']:.2f}") | ||||||
|  | 
 | ||||||
|  |     assert summary['tps_avg'] > 0, "Average TPS should be greater than zero" | ||||||
|  |     assert summary['ticks_elapsed_time'] > 0, "Elapsed time should be greater than zero" | ||||||
| @ -1,10 +1,12 @@ | |||||||
| import pytest | import pytest | ||||||
| from world.world import World, Position, BaseEntity | from world.world import World, Position, BaseEntity, Rotation | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class DummyEntity(BaseEntity): | class DummyEntity(BaseEntity): | ||||||
|     def __init__(self, position): |     def __init__(self, position, rotation=None): | ||||||
|         super().__init__(position) |         if rotation is None: | ||||||
|  |             rotation = Rotation(angle=0) | ||||||
|  |         super().__init__(position, rotation) | ||||||
|         self.ticked = False |         self.ticked = False | ||||||
|         self.rendered = False |         self.rendered = False | ||||||
| 
 | 
 | ||||||
| @ -83,9 +85,6 @@ def test_tick_all_calls_tick(world): | |||||||
| 
 | 
 | ||||||
| def test_add_object_out_of_bounds(world): | def test_add_object_out_of_bounds(world): | ||||||
|     entity = DummyEntity(Position(x=1000, y=1000)) |     entity = DummyEntity(Position(x=1000, y=1000)) | ||||||
| 
 |  | ||||||
|     world.add_object(entity) |     world.add_object(entity) | ||||||
| 
 |  | ||||||
|     entity = world.get_objects()[0] |     entity = world.get_objects()[0] | ||||||
| 
 |     assert entity.position.x == 49 and entity.position.y == 49 | ||||||
|     assert entity.position.x == 49 and entity.position.y == 49 |  | ||||||
							
								
								
									
										217
									
								
								ui/hud.py
									
									
									
									
									
								
							
							
						
						
									
										217
									
								
								ui/hud.py
									
									
									
									
									
								
							| @ -6,18 +6,188 @@ import pygame_gui | |||||||
| from config.constants import * | from config.constants import * | ||||||
| from world.base.brain import CellBrain, FlexibleNeuralNetwork | from world.base.brain import CellBrain, FlexibleNeuralNetwork | ||||||
| from world.objects import DefaultCell | from world.objects import DefaultCell | ||||||
|  | from pygame_gui.elements import UIPanel | ||||||
| import math | import math | ||||||
| 
 | 
 | ||||||
|  | DARK_GRAY = (40, 40, 40) | ||||||
|  | DARKER_GRAY = (25, 25, 25) | ||||||
| 
 | 
 | ||||||
| class HUD: | class HUD: | ||||||
|     def __init__(self, ui_manager, screen_width=SCREEN_WIDTH, screen_height=SCREEN_HEIGHT): |     def __init__(self, ui_manager, screen_width=SCREEN_WIDTH, screen_height=SCREEN_HEIGHT): | ||||||
|         self.font = pygame.font.Font("freesansbold.ttf", FONT_SIZE) |         self.font = pygame.font.Font("freesansbold.ttf", FONT_SIZE) | ||||||
|         self.legend_font = pygame.font.Font("freesansbold.ttf", LEGEND_FONT_SIZE) |         self.legend_font = pygame.font.Font("freesansbold.ttf", LEGEND_FONT_SIZE) | ||||||
| 
 | 
 | ||||||
|  |         self.manager = ui_manager | ||||||
|         self.screen_width = screen_width |         self.screen_width = screen_width | ||||||
|         self.screen_height = screen_height |         self.screen_height = screen_height | ||||||
| 
 | 
 | ||||||
|         self.manager = ui_manager |         # Panel size defaults | ||||||
|  |         self.control_bar_height = 48 | ||||||
|  |         self.inspector_width = 260 | ||||||
|  |         self.properties_width = 320 | ||||||
|  |         self.console_height = 120 | ||||||
|  |         self.splitter_thickness = 6 | ||||||
|  | 
 | ||||||
|  |         self.dragging_splitter = None | ||||||
|  |         self._create_panels() | ||||||
|  | 
 | ||||||
|  |     def _create_panels(self): | ||||||
|  |         # Top control bar | ||||||
|  |         self.control_bar = UIPanel( | ||||||
|  |             relative_rect=pygame.Rect(0, 0, self.screen_width, self.control_bar_height), | ||||||
|  |             manager=self.manager, | ||||||
|  |             object_id="#control_bar", | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Left inspector | ||||||
|  |         self.inspector_panel = UIPanel( | ||||||
|  |             relative_rect=pygame.Rect( | ||||||
|  |                 0, self.control_bar_height, | ||||||
|  |                 self.inspector_width, | ||||||
|  |                 self.screen_height - self.control_bar_height | ||||||
|  |             ), | ||||||
|  |             manager=self.manager, | ||||||
|  |             object_id="#inspector_panel", | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Right properties | ||||||
|  |         self.properties_panel = UIPanel( | ||||||
|  |             relative_rect=pygame.Rect( | ||||||
|  |                 self.screen_width - self.properties_width, | ||||||
|  |                 self.control_bar_height, | ||||||
|  |                 self.properties_width, | ||||||
|  |                 self.screen_height - self.control_bar_height | ||||||
|  |             ), | ||||||
|  |             manager=self.manager, | ||||||
|  |             object_id="#properties_panel", | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Bottom console | ||||||
|  |         self.console_panel = UIPanel( | ||||||
|  |             relative_rect=pygame.Rect( | ||||||
|  |                 self.inspector_width, | ||||||
|  |                 self.screen_height - self.console_height, | ||||||
|  |                 self.screen_width - self.inspector_width - self.properties_width, | ||||||
|  |                 self.console_height | ||||||
|  |             ), | ||||||
|  |             manager=self.manager, | ||||||
|  |             object_id="#console_panel", | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         self.panels = [ | ||||||
|  |             self.control_bar, | ||||||
|  |             self.inspector_panel, | ||||||
|  |             self.properties_panel, | ||||||
|  |             self.console_panel | ||||||
|  |         ] | ||||||
|  |         self.dragging_splitter = None | ||||||
|  | 
 | ||||||
|  |     def get_viewport_rect(self): | ||||||
|  |         # Returns the rect for the simulation viewport | ||||||
|  |         x = self.inspector_width | ||||||
|  |         y = self.control_bar_height | ||||||
|  |         w = self.screen_width - self.inspector_width - self.properties_width | ||||||
|  |         h = self.screen_height - self.control_bar_height - self.console_height | ||||||
|  |         return pygame.Rect(x, y, w, h) | ||||||
|  | 
 | ||||||
|  |     def update_layout(self, window_width, window_height): | ||||||
|  |         self.screen_width = window_width | ||||||
|  |         self.screen_height = window_height | ||||||
|  | 
 | ||||||
|  |         # Control bar (top) | ||||||
|  |         self.control_bar.set_relative_position((0, 0)) | ||||||
|  |         self.control_bar.set_dimensions((self.screen_width, self.control_bar_height)) | ||||||
|  | 
 | ||||||
|  |         # Inspector panel (left) - goes all the way to the bottom | ||||||
|  |         self.inspector_panel.set_relative_position((0, self.control_bar_height)) | ||||||
|  |         self.inspector_panel.set_dimensions((self.inspector_width, self.screen_height - self.control_bar_height)) | ||||||
|  | 
 | ||||||
|  |         # Properties panel (right) - goes all the way to the bottom | ||||||
|  |         self.properties_panel.set_relative_position( | ||||||
|  |             (self.screen_width - self.properties_width, self.control_bar_height)) | ||||||
|  |         self.properties_panel.set_dimensions((self.properties_width, self.screen_height - self.control_bar_height)) | ||||||
|  | 
 | ||||||
|  |         # Console panel (bottom, spans between inspector and properties) | ||||||
|  |         self.console_panel.set_relative_position((self.inspector_width, self.screen_height - self.console_height)) | ||||||
|  |         self.console_panel.set_dimensions( | ||||||
|  |             (self.screen_width - self.inspector_width - self.properties_width, self.console_height)) | ||||||
|  | 
 | ||||||
|  |     def process_event(self, event): | ||||||
|  |         # Handle splitter dragging for resizing panels | ||||||
|  |         if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: | ||||||
|  |             mx, my = event.pos | ||||||
|  |             # Check if mouse is on a splitter (left/right/bottom) | ||||||
|  |             if abs(mx - self.inspector_width) < self.splitter_thickness and self.control_bar_height < my < self.screen_height - self.console_height: | ||||||
|  |                 self.dragging_splitter = "inspector" | ||||||
|  |             elif abs(mx - (self.screen_width - self.properties_width)) < self.splitter_thickness and self.control_bar_height < my < self.screen_height - self.console_height: | ||||||
|  |                 self.dragging_splitter = "properties" | ||||||
|  |             elif abs(my - (self.screen_height - self.console_height)) < self.splitter_thickness and self.inspector_width < mx < self.screen_width - self.properties_width: | ||||||
|  |                 self.dragging_splitter = "console" | ||||||
|  |             self.update_layout(self.screen_width, self.screen_height) | ||||||
|  |         elif event.type == pygame.MOUSEBUTTONUP and event.button == 1: | ||||||
|  |             self.dragging_splitter = None | ||||||
|  |         elif event.type == pygame.MOUSEMOTION and self.dragging_splitter: | ||||||
|  |             mx, my = event.pos | ||||||
|  |             if self.dragging_splitter == "inspector": | ||||||
|  |                 self.inspector_width = max(100, min(mx, self.screen_width - self.properties_width - 100)) | ||||||
|  |             elif self.dragging_splitter == "properties": | ||||||
|  |                 self.properties_width = max(100, min(self.screen_width - mx, self.screen_width - self.inspector_width - 100)) | ||||||
|  |             elif self.dragging_splitter == "console": | ||||||
|  |                 self.console_height = max(60, min(self.screen_height - my, self.screen_height - self.control_bar_height - 60)) | ||||||
|  |             self.update_layout(self.screen_width, self.screen_height) | ||||||
|  | 
 | ||||||
|  |     def draw_splitters(self, screen): | ||||||
|  |         # Draw draggable splitters for visual feedback | ||||||
|  |         indicator_color = (220, 220, 220) | ||||||
|  |         indicator_size = 6  # Length of indicator line | ||||||
|  |         indicator_gap = 4    # Gap between indicator lines | ||||||
|  |         indicator_count = 3  # Number of indicator lines | ||||||
|  | 
 | ||||||
|  |         # Vertical splitter (inspector/properties) | ||||||
|  |         # Inspector/properties only if wide enough | ||||||
|  |         if self.inspector_width > 0: | ||||||
|  |             x = self.inspector_width - 2 | ||||||
|  |             y1 = self.control_bar_height | ||||||
|  |             y2 = self.screen_height - self.console_height | ||||||
|  |             # Draw indicator (horizontal lines) in the middle | ||||||
|  |             mid_y = (y1 + y2) // 2 | ||||||
|  |             for i in range(indicator_count): | ||||||
|  |                 offset = (i - 1) * (indicator_gap + 1) | ||||||
|  |                 pygame.draw.line( | ||||||
|  |                     screen, indicator_color, | ||||||
|  |                     (x - indicator_size // 2, mid_y + offset), | ||||||
|  |                     (x + indicator_size // 2, mid_y + offset), | ||||||
|  |                     2 | ||||||
|  |                 ) | ||||||
|  | 
 | ||||||
|  |         if self.properties_width > 0: | ||||||
|  |             x = self.screen_width - self.properties_width + 2 | ||||||
|  |             y1 = self.control_bar_height | ||||||
|  |             y2 = self.screen_height - self.console_height | ||||||
|  |             mid_y = (y1 + y2) // 2 | ||||||
|  |             for i in range(indicator_count): | ||||||
|  |                 offset = (i - 1) * (indicator_gap + 1) | ||||||
|  |                 pygame.draw.line( | ||||||
|  |                     screen, indicator_color, | ||||||
|  |                     (x - indicator_size // 2, mid_y + offset), | ||||||
|  |                     (x + indicator_size // 2, mid_y + offset), | ||||||
|  |                     2 | ||||||
|  |                 ) | ||||||
|  | 
 | ||||||
|  |         # Horizontal splitter (console) | ||||||
|  |         if self.console_height > 0: | ||||||
|  |             y = self.screen_height - self.console_height + 2 | ||||||
|  |             x1 = self.inspector_width | ||||||
|  |             x2 = self.screen_width - self.properties_width | ||||||
|  |             mid_x = (x1 + x2) // 2 | ||||||
|  |             for i in range(indicator_count): | ||||||
|  |                 offset = (i - 1) * (indicator_gap + 1) | ||||||
|  |                 pygame.draw.line( | ||||||
|  |                     screen, indicator_color, | ||||||
|  |                     (mid_x + offset, y - indicator_size // 2), | ||||||
|  |                     (mid_x + offset, y + indicator_size // 2), | ||||||
|  |                     2 | ||||||
|  |                 ) | ||||||
| 
 | 
 | ||||||
|     def render_mouse_position(self, screen, camera, sim_view_rect): |     def render_mouse_position(self, screen, camera, sim_view_rect): | ||||||
|         """Render mouse position in top left.""" |         """Render mouse position in top left.""" | ||||||
| @ -145,6 +315,7 @@ class HUD: | |||||||
|         VIZ_WIDTH = 280  # Width of the neural network visualization area |         VIZ_WIDTH = 280  # Width of the neural network visualization area | ||||||
|         VIZ_HEIGHT = 300  # Height of the neural network visualization area |         VIZ_HEIGHT = 300  # Height of the neural network visualization area | ||||||
|         VIZ_RIGHT_MARGIN = VIZ_WIDTH + 50  # Distance from right edge of screen to visualization |         VIZ_RIGHT_MARGIN = VIZ_WIDTH + 50  # Distance from right edge of screen to visualization | ||||||
|  |         VIZ_BOTTOM_MARGIN = 50  # Distance from the bottom of the screen | ||||||
| 
 | 
 | ||||||
|         # Background styling constants |         # Background styling constants | ||||||
|         BACKGROUND_PADDING = 30  # Padding around the visualization background |         BACKGROUND_PADDING = 30  # Padding around the visualization background | ||||||
| @ -196,6 +367,9 @@ class HUD: | |||||||
|         TOOLTIP_MARGIN = 10 |         TOOLTIP_MARGIN = 10 | ||||||
|         TOOLTIP_LINE_SPACING = 0  # No extra spacing between lines |         TOOLTIP_LINE_SPACING = 0  # No extra spacing between lines | ||||||
| 
 | 
 | ||||||
|  |         if self.properties_width < VIZ_RIGHT_MARGIN + 50: | ||||||
|  |             self.properties_width = VIZ_RIGHT_MARGIN + 50 # Ensure properties panel is wide enough for tooltip | ||||||
|  | 
 | ||||||
|         if not hasattr(cell, 'behavioral_model'): |         if not hasattr(cell, 'behavioral_model'): | ||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
| @ -206,9 +380,9 @@ class HUD: | |||||||
| 
 | 
 | ||||||
|         network: FlexibleNeuralNetwork = cell_brain.neural_network |         network: FlexibleNeuralNetwork = cell_brain.neural_network | ||||||
| 
 | 
 | ||||||
|         # Calculate visualization position |         # Calculate visualization position (bottom right) | ||||||
|         viz_x = self.screen_width - VIZ_RIGHT_MARGIN  # Right side of screen |         viz_x = self.screen_width - VIZ_RIGHT_MARGIN  # Right side of screen | ||||||
|         viz_y = (self.screen_height // 2) - (VIZ_HEIGHT // 2)  # Centered vertically |         viz_y = self.screen_height - VIZ_HEIGHT - VIZ_BOTTOM_MARGIN  # Above the bottom margin | ||||||
| 
 | 
 | ||||||
|         layer_spacing = VIZ_WIDTH // max(1, len(network.layers) - 1) if len(network.layers) > 1 else VIZ_WIDTH |         layer_spacing = VIZ_WIDTH // max(1, len(network.layers) - 1) if len(network.layers) > 1 else VIZ_WIDTH | ||||||
| 
 | 
 | ||||||
| @ -218,6 +392,8 @@ class HUD: | |||||||
|         pygame.draw.rect(screen, BACKGROUND_COLOR, background_rect) |         pygame.draw.rect(screen, BACKGROUND_COLOR, background_rect) | ||||||
|         pygame.draw.rect(screen, WHITE, background_rect, BACKGROUND_BORDER_WIDTH) |         pygame.draw.rect(screen, WHITE, background_rect, BACKGROUND_BORDER_WIDTH) | ||||||
| 
 | 
 | ||||||
|  |         info = network.get_structure_info() | ||||||
|  | 
 | ||||||
|         # Title |         # Title | ||||||
|         title_text = self.font.render("Neural Network", True, WHITE) |         title_text = self.font.render("Neural Network", True, WHITE) | ||||||
|         title_rect = title_text.get_rect() |         title_rect = title_text.get_rect() | ||||||
| @ -225,6 +401,13 @@ class HUD: | |||||||
|         title_rect.top = viz_y - TITLE_TOP_MARGIN |         title_rect.top = viz_y - TITLE_TOP_MARGIN | ||||||
|         screen.blit(title_text, title_rect) |         screen.blit(title_text, title_rect) | ||||||
| 
 | 
 | ||||||
|  |         # Render network cost under the title | ||||||
|  |         cost_text = self.font.render(f"Cost: {info['network_cost']}", True, WHITE) | ||||||
|  |         cost_rect = cost_text.get_rect() | ||||||
|  |         cost_rect.centerx = title_rect.centerx | ||||||
|  |         cost_rect.top = title_rect.bottom + 4  # Small gap below the title | ||||||
|  |         screen.blit(cost_text, cost_rect) | ||||||
|  | 
 | ||||||
|         # Get current activations by running a forward pass with current inputs |         # Get current activations by running a forward pass with current inputs | ||||||
|         input_values = [cell_brain.inputs[key] for key in cell_brain.input_keys] |         input_values = [cell_brain.inputs[key] for key in cell_brain.input_keys] | ||||||
| 
 | 
 | ||||||
| @ -383,22 +566,6 @@ class HUD: | |||||||
|                 label_rect.bottom = viz_y + VIZ_HEIGHT + LAYER_LABEL_BOTTOM_MARGIN |                 label_rect.bottom = viz_y + VIZ_HEIGHT + LAYER_LABEL_BOTTOM_MARGIN | ||||||
|                 screen.blit(label_text, label_rect) |                 screen.blit(label_text, label_rect) | ||||||
| 
 | 
 | ||||||
|         # Draw network info |  | ||||||
|         info = network.get_structure_info() |  | ||||||
|         info_lines = [ |  | ||||||
|             f"Layers: {info['total_layers']}", |  | ||||||
|             f"Neurons: {info['total_neurons']}", |  | ||||||
|             f"Connections: {info['total_connections']}", |  | ||||||
|             f"Network Cost: {info['network_cost']}", |  | ||||||
|         ] |  | ||||||
| 
 |  | ||||||
|         for i, line in enumerate(info_lines): |  | ||||||
|             info_text = self.legend_font.render(line, True, WHITE) |  | ||||||
|             info_rect = info_text.get_rect() |  | ||||||
|             info_rect.left = viz_x |  | ||||||
|             info_rect.top = viz_y + VIZ_HEIGHT + INFO_TEXT_TOP_MARGIN + i * INFO_TEXT_LINE_SPACING |  | ||||||
|             screen.blit(info_text, info_rect) |  | ||||||
| 
 |  | ||||||
|         # --- Tooltip logic for neuron hover --- |         # --- Tooltip logic for neuron hover --- | ||||||
|         mouse_x, mouse_y = pygame.mouse.get_pos() |         mouse_x, mouse_y = pygame.mouse.get_pos() | ||||||
|         tooltip_text = None |         tooltip_text = None | ||||||
| @ -474,22 +641,20 @@ class HUD: | |||||||
|                 screen.blit(surf, (tooltip_rect.left + TOOLTIP_PADDING_X, y)) |                 screen.blit(surf, (tooltip_rect.left + TOOLTIP_PADDING_X, y)) | ||||||
|                 y += surf.get_height() + TOOLTIP_LINE_SPACING |                 y += surf.get_height() + TOOLTIP_LINE_SPACING | ||||||
| 
 | 
 | ||||||
|     def render_sprint_debug(self, screen, actual_tps, total_ticks): |     def render_sprint_debug(self, screen, actual_tps, total_ticks, cell_count=None): | ||||||
|         """Render sprint debug info: header, TPS, and tick count.""" |         """Render sprint debug info: header, TPS, and tick count.""" | ||||||
|         header = self.font.render("Sprinting...", True, (255, 200, 0)) |         header = self.font.render("Sprinting...", True, (255, 200, 0)) | ||||||
|         tps_text = self.font.render(f"TPS: {actual_tps}", True, (255, 255, 255)) |         tps_text = self.font.render(f"TPS: {actual_tps}", True, (255, 255, 255)) | ||||||
|         ticks_text = self.font.render(f"Ticks: {total_ticks}", True, (255, 255, 255)) |         ticks_text = self.font.render(f"Ticks: {total_ticks}", True, (255, 255, 255)) | ||||||
|  |         cell_text = self.font.render(f"Cells: {cell_count}" if cell_count is not None else "Cells: N/A", True, (255, 255, 255)) | ||||||
| 
 | 
 | ||||||
|         y = self.screen_height // 2 - 40 |         y = self.screen_height // 2 - 80 | ||||||
|         header_rect = header.get_rect(center=(self.screen_width // 2, y)) |         header_rect = header.get_rect(center=(self.screen_width // 2, y)) | ||||||
|         tps_rect = tps_text.get_rect(center=(self.screen_width // 2, y + 40)) |         tps_rect = tps_text.get_rect(center=(self.screen_width // 2, y + 40)) | ||||||
|         ticks_rect = ticks_text.get_rect(center=(self.screen_width // 2, y + 80)) |         ticks_rect = ticks_text.get_rect(center=(self.screen_width // 2, y + 80)) | ||||||
|  |         cell_rect = cell_text.get_rect(center=(self.screen_width // 2, y + 120)) | ||||||
| 
 | 
 | ||||||
|         screen.blit(header, header_rect) |         screen.blit(header, header_rect) | ||||||
|         screen.blit(tps_text, tps_rect) |         screen.blit(tps_text, tps_rect) | ||||||
|         screen.blit(ticks_text, ticks_rect) |         screen.blit(ticks_text, ticks_rect) | ||||||
| 
 |         screen.blit(cell_text, cell_rect) | ||||||
|     def update_layout(self, window_width, window_height): |  | ||||||
|         """Update HUD layout on window resize.""" |  | ||||||
|         self.screen_width = window_width |  | ||||||
|         self.screen_height = window_height |  | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										21
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							| @ -53,6 +53,7 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [package.dev-dependencies] | [package.dev-dependencies] | ||||||
| dev = [ | dev = [ | ||||||
|  |     { name = "psutil" }, | ||||||
|     { name = "ruff" }, |     { name = "ruff" }, | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| @ -67,7 +68,10 @@ requires-dist = [ | |||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [package.metadata.requires-dev] | [package.metadata.requires-dev] | ||||||
| dev = [{ name = "ruff", specifier = ">=0.11.12" }] | dev = [ | ||||||
|  |     { name = "psutil", specifier = ">=7.0.0" }, | ||||||
|  |     { name = "ruff", specifier = ">=0.11.12" }, | ||||||
|  | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "filelock" | name = "filelock" | ||||||
| @ -206,6 +210,21 @@ wheels = [ | |||||||
|     { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, |     { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "psutil" | ||||||
|  | version = "7.0.0" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "pydantic" | name = "pydantic" | ||||||
| version = "2.11.5" | version = "2.11.5" | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ import pygame | |||||||
| from typing import Optional, List, Any, Union | from typing import Optional, List, Any, Union | ||||||
| 
 | 
 | ||||||
| from world.utils import get_distance_between_objects | from world.utils import get_distance_between_objects | ||||||
|  | from world.physics import Physics | ||||||
| 
 | 
 | ||||||
| from math import atan2, degrees | from math import atan2, degrees | ||||||
| 
 | 
 | ||||||
| @ -263,6 +264,8 @@ class DefaultCell(BaseEntity): | |||||||
| 
 | 
 | ||||||
|         self.tick_count = 0 |         self.tick_count = 0 | ||||||
| 
 | 
 | ||||||
|  |         self.physics = Physics(0.02, 0.05) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|     def set_brain(self, behavioral_model: CellBrain) -> None: |     def set_brain(self, behavioral_model: CellBrain) -> None: | ||||||
|         self.behavioral_model = behavioral_model |         self.behavioral_model = behavioral_model | ||||||
| @ -297,7 +300,7 @@ class DefaultCell(BaseEntity): | |||||||
|         distance_to_food = get_distance_between_objects(self, food_object) |         distance_to_food = get_distance_between_objects(self, food_object) | ||||||
| 
 | 
 | ||||||
|         if distance_to_food < self.max_visual_width and food_objects: |         if distance_to_food < self.max_visual_width and food_objects: | ||||||
|             self.energy += 110 |             self.energy += 130 | ||||||
|             food_object.flag_for_death() |             food_object.flag_for_death() | ||||||
|             return self |             return self | ||||||
| 
 | 
 | ||||||
| @ -312,10 +315,10 @@ class DefaultCell(BaseEntity): | |||||||
|             duplicate_y_2 += random.randint(-self.max_visual_width, self.max_visual_width) |             duplicate_y_2 += random.randint(-self.max_visual_width, self.max_visual_width) | ||||||
| 
 | 
 | ||||||
|             new_cell = DefaultCell(Position(x=int(duplicate_x), y=int(duplicate_y)), Rotation(angle=random.randint(0, 359))) |             new_cell = DefaultCell(Position(x=int(duplicate_x), y=int(duplicate_y)), Rotation(angle=random.randint(0, 359))) | ||||||
|             new_cell.set_brain(self.behavioral_model.mutate(0.4)) |             new_cell.set_brain(self.behavioral_model.mutate(0.05)) | ||||||
| 
 | 
 | ||||||
|             new_cell_2 = DefaultCell(Position(x=int(duplicate_x_2), y=int(duplicate_y_2)), Rotation(angle=random.randint(0, 359))) |             new_cell_2 = DefaultCell(Position(x=int(duplicate_x_2), y=int(duplicate_y_2)), Rotation(angle=random.randint(0, 359))) | ||||||
|             new_cell_2.set_brain(self.behavioral_model.mutate(0.4)) |             new_cell_2.set_brain(self.behavioral_model.mutate(0.05)) | ||||||
| 
 | 
 | ||||||
|             return [new_cell, new_cell_2] |             return [new_cell, new_cell_2] | ||||||
| 
 | 
 | ||||||
| @ -328,44 +331,12 @@ class DefaultCell(BaseEntity): | |||||||
| 
 | 
 | ||||||
|         output_data = self.behavioral_model.tick(input_data) |         output_data = self.behavioral_model.tick(input_data) | ||||||
| 
 | 
 | ||||||
|         # everything below this point is physics simulation and needs to be extracted to a separate class |  | ||||||
| 
 |  | ||||||
|         # clamp accelerations |         # clamp accelerations | ||||||
|         output_data["linear_acceleration"] = max(-MAX_ACCELERATION, min(MAX_ACCELERATION, output_data["linear_acceleration"])) |         output_data["linear_acceleration"] = max(-MAX_ACCELERATION, min(MAX_ACCELERATION, output_data["linear_acceleration"])) | ||||||
|         output_data["angular_acceleration"] = max(-MAX_ANGULAR_ACCELERATION, min(MAX_ANGULAR_ACCELERATION, output_data["angular_acceleration"])) |         output_data["angular_acceleration"] = max(-MAX_ANGULAR_ACCELERATION, min(MAX_ANGULAR_ACCELERATION, output_data["angular_acceleration"])) | ||||||
| 
 | 
 | ||||||
|         # 2. Apply drag force |         # request physics data from Physics class | ||||||
|         drag_coefficient = 0.02 |         self.velocity, self.acceleration, self.rotational_velocity, self.angular_acceleration = self.physics.move(output_data["linear_acceleration"], output_data["angular_acceleration"], self.rotation.get_rotation()) | ||||||
|         drag_x = -self.velocity[0] * drag_coefficient |  | ||||||
|         drag_y = -self.velocity[1] * drag_coefficient |  | ||||||
| 
 |  | ||||||
|         # 3. Combine all forces |  | ||||||
|         total_linear_accel = output_data["linear_acceleration"] |  | ||||||
|         total_linear_accel = max(-0.1, min(0.1, total_linear_accel)) |  | ||||||
| 
 |  | ||||||
|         # 4. Convert to world coordinates |  | ||||||
|         x_component = total_linear_accel * math.cos(math.radians(self.rotation.get_rotation())) |  | ||||||
|         y_component = total_linear_accel * math.sin(math.radians(self.rotation.get_rotation())) |  | ||||||
| 
 |  | ||||||
|         # 5. Add drag to total acceleration |  | ||||||
|         total_accel_x = x_component + drag_x |  | ||||||
|         total_accel_y = y_component + drag_y |  | ||||||
| 
 |  | ||||||
|         self.acceleration = (total_accel_x, total_accel_y) |  | ||||||
| 
 |  | ||||||
|         rotational_drag = 0.05 |  | ||||||
|         self.angular_acceleration = output_data["angular_acceleration"] - self.rotational_velocity * rotational_drag |  | ||||||
| 
 |  | ||||||
|         # tick acceleration |  | ||||||
|         velocity_x = self.velocity[0] + self.acceleration[0] |  | ||||||
|         velocity_y = self.velocity[1] + self.acceleration[1] |  | ||||||
|         self.velocity = (velocity_x, velocity_y) |  | ||||||
| 
 |  | ||||||
|         # # clamp velocity |  | ||||||
|         speed = math.sqrt(self.velocity[0] ** 2 + self.velocity[1] ** 2) |  | ||||||
|         if speed > MAX_VELOCITY: |  | ||||||
|             scale = MAX_VELOCITY / speed |  | ||||||
|             self.velocity = (self.velocity[0] * scale, self.velocity[1] * scale) |  | ||||||
| 
 | 
 | ||||||
|         # tick velocity |         # tick velocity | ||||||
|         x, y = self.position.get_position() |         x, y = self.position.get_position() | ||||||
| @ -374,19 +345,12 @@ class DefaultCell(BaseEntity): | |||||||
| 
 | 
 | ||||||
|         self.position.set_position(x, y) |         self.position.set_position(x, y) | ||||||
| 
 | 
 | ||||||
|         # tick rotational acceleration |  | ||||||
|         self.angular_acceleration = output_data["angular_acceleration"] |  | ||||||
|         self.rotational_velocity += self.angular_acceleration |  | ||||||
| 
 |  | ||||||
|         # clamp rotational velocity |  | ||||||
|         self.rotational_velocity = max(-MAX_ROTATIONAL_VELOCITY, min(MAX_ROTATIONAL_VELOCITY, self.rotational_velocity)) |  | ||||||
| 
 |  | ||||||
|         # tick rotational velocity |         # tick rotational velocity | ||||||
|         self.rotation.set_rotation(self.rotation.get_rotation() + self.rotational_velocity) |         self.rotation.set_rotation(self.rotation.get_rotation() + self.rotational_velocity) | ||||||
| 
 | 
 | ||||||
|         movement_cost = abs(output_data["angular_acceleration"]) + abs(output_data["linear_acceleration"]) |         movement_cost = abs(output_data["angular_acceleration"]) + abs(output_data["linear_acceleration"]) | ||||||
| 
 | 
 | ||||||
|         self.energy -= (self.behavioral_model.neural_network.network_cost * 0.01) + 1 + (0.5 * movement_cost) |         self.energy -= (self.behavioral_model.neural_network.network_cost * 0.1) + 1.2 + (0.15 * movement_cost) | ||||||
| 
 | 
 | ||||||
|         return self |         return self | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										82
									
								
								world/physics.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								world/physics.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | |||||||
|  | import math | ||||||
|  | 
 | ||||||
|  | from config.constants import MAX_VELOCITY, MAX_ROTATIONAL_VELOCITY | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Physics: | ||||||
|  |     """ | ||||||
|  |     Simulates basic 2D physics for an object, including linear and rotational motion | ||||||
|  |     with drag effects. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, drag_coefficient: float, rotational_drag: float): | ||||||
|  |         """ | ||||||
|  |         Initialize the Physics object. | ||||||
|  | 
 | ||||||
|  |         Args: | ||||||
|  |             drag_coefficient (float): Linear drag coefficient. | ||||||
|  |             rotational_drag (float): Rotational drag coefficient. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |         self.drag_coefficient: float = drag_coefficient | ||||||
|  |         self.rotational_drag: float = rotational_drag | ||||||
|  | 
 | ||||||
|  |         self.velocity: tuple[int, int] = (0, 0) | ||||||
|  |         self.acceleration: tuple[int, int] = (0, 0) | ||||||
|  | 
 | ||||||
|  |         self.rotational_velocity: int = 0 | ||||||
|  |         self.angular_acceleration: int = 0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def move(self, linear_acceleration: float, angular_acceleration: int, rotational_position): | ||||||
|  |         """ | ||||||
|  |         Update the object's velocity and acceleration based on input forces and drag. | ||||||
|  | 
 | ||||||
|  |         Args: | ||||||
|  |             linear_acceleration (float): The applied linear acceleration. | ||||||
|  |             angular_acceleration (int): The applied angular acceleration. | ||||||
|  |             rotational_position: The current rotational position in degrees. | ||||||
|  | 
 | ||||||
|  |         Returns: | ||||||
|  |             tuple: Updated (velocity, acceleration, rotational_velocity, angular_acceleration). | ||||||
|  |         """ | ||||||
|  |         # Apply drag force | ||||||
|  |         drag_coefficient = self.drag_coefficient | ||||||
|  |         drag_x = -self.velocity[0] * drag_coefficient | ||||||
|  |         drag_y = -self.velocity[1] * drag_coefficient | ||||||
|  | 
 | ||||||
|  |         # Combine all forces | ||||||
|  |         total_linear_accel = linear_acceleration | ||||||
|  |         total_linear_accel = max(-0.1, min(0.1, total_linear_accel)) | ||||||
|  | 
 | ||||||
|  |         # Convert to world coordinates | ||||||
|  |         x_component = total_linear_accel * math.cos(math.radians(rotational_position)) | ||||||
|  |         y_component = total_linear_accel * math.sin(math.radians(rotational_position)) | ||||||
|  | 
 | ||||||
|  |         # Add drag to total acceleration | ||||||
|  |         total_accel_x = x_component + drag_x | ||||||
|  |         total_accel_y = y_component + drag_y | ||||||
|  | 
 | ||||||
|  |         self.acceleration = (total_accel_x, total_accel_y) | ||||||
|  | 
 | ||||||
|  |         # Apply drag force to angular acceleration | ||||||
|  |         rotational_drag = self.rotational_drag | ||||||
|  |         self.angular_acceleration = angular_acceleration - self.rotational_velocity * rotational_drag | ||||||
|  | 
 | ||||||
|  |         # tick acceleration | ||||||
|  |         velocity_x = self.velocity[0] + self.acceleration[0] | ||||||
|  |         velocity_y = self.velocity[1] + self.acceleration[1] | ||||||
|  |         self.velocity = (velocity_x, velocity_y) | ||||||
|  | 
 | ||||||
|  |         # clamp velocity | ||||||
|  |         speed = math.sqrt(self.velocity[0] ** 2 + self.velocity[1] ** 2) | ||||||
|  |         if speed > MAX_VELOCITY: | ||||||
|  |             scale = MAX_VELOCITY / speed | ||||||
|  |             self.velocity = (self.velocity[0] * scale, self.velocity[1] * scale) | ||||||
|  | 
 | ||||||
|  |         self.angular_acceleration = angular_acceleration | ||||||
|  |         self.rotational_velocity += self.angular_acceleration | ||||||
|  | 
 | ||||||
|  |         # clamp rotational velocity | ||||||
|  |         self.rotational_velocity = max(-MAX_ROTATIONAL_VELOCITY, min(MAX_ROTATIONAL_VELOCITY, self.rotational_velocity)) | ||||||
|  | 
 | ||||||
|  |         return self.velocity, self.acceleration, self.rotational_velocity, self.angular_acceleration | ||||||
| @ -153,6 +153,8 @@ class World: | |||||||
| 
 | 
 | ||||||
|         :param camera: The camera object for coordinate transformation. |         :param camera: The camera object for coordinate transformation. | ||||||
|         :param screen: The Pygame screen surface. |         :param screen: The Pygame screen surface. | ||||||
|  | 
 | ||||||
|  |         Time complexity: O(n), where n is the number of objects in the current buffer. | ||||||
|         """ |         """ | ||||||
|         for obj_list in self.buffers[self.current_buffer].values(): |         for obj_list in self.buffers[self.current_buffer].values(): | ||||||
|             for obj in obj_list: |             for obj in obj_list: | ||||||
| @ -161,6 +163,9 @@ class World: | |||||||
|     def tick_all(self) -> None: |     def tick_all(self) -> None: | ||||||
|         """ |         """ | ||||||
|         Advances all objects in the world by one tick, updating their state and handling interactions. |         Advances all objects in the world by one tick, updating their state and handling interactions. | ||||||
|  | 
 | ||||||
|  |         Time complexity: O(N + K) / O(N*M), where N is the number of objects in the current buffer, | ||||||
|  |         K is the number of objects that can interact with each object, and M is number of objects in checked cells where C is the number of cells checked within the interaction radius. | ||||||
|         """ |         """ | ||||||
|         next_buffer: int = 1 - self.current_buffer |         next_buffer: int = 1 - self.current_buffer | ||||||
|         self.buffers[next_buffer].clear() |         self.buffers[next_buffer].clear() | ||||||
| @ -208,6 +213,8 @@ class World: | |||||||
|         :param y: Y coordinate of the center. |         :param y: Y coordinate of the center. | ||||||
|         :param radius: Search radius. |         :param radius: Search radius. | ||||||
|         :return: List of objects within the radius. |         :return: List of objects within the radius. | ||||||
|  | 
 | ||||||
|  |         Time complexity: O(C * M) / O(N), where C is the number of cells checked within the radius and M is the number of objects in those cells. | ||||||
|         """ |         """ | ||||||
|         result: List[BaseEntity] = [] |         result: List[BaseEntity] = [] | ||||||
|         cell_x, cell_y = int(x // self.partition_size), int(y // self.partition_size) |         cell_x, cell_y = int(x // self.partition_size), int(y // self.partition_size) | ||||||
| @ -234,6 +241,8 @@ class World: | |||||||
|         :param x2: Maximum X coordinate. |         :param x2: Maximum X coordinate. | ||||||
|         :param y2: Maximum Y coordinate. |         :param y2: Maximum Y coordinate. | ||||||
|         :return: List of objects within the rectangle. |         :return: List of objects within the rectangle. | ||||||
|  | 
 | ||||||
|  |         Time complexity: O(C * M) / O(N), where C is the number of cells checked within the rectangle and M is the number of objects in those cells. | ||||||
|         """ |         """ | ||||||
|         result: List[BaseEntity] = [] |         result: List[BaseEntity] = [] | ||||||
|         cell_x1, cell_y1 = ( |         cell_x1, cell_y1 = ( | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user