Add rotation and behavioral model to entities, implement DefaultCell entity
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build Simulation and Test / Run All Tests (push) Failing after 35s
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build Simulation and Test / Run All Tests (push) Failing after 35s
				
			This commit is contained in:
		
							parent
							
								
									589bb13688
								
							
						
					
					
						commit
						f0576e52d6
					
				
							
								
								
									
										189
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										189
									
								
								main.py
									
									
									
									
									
								
							| @ -1,10 +1,12 @@ | ||||
| import math | ||||
| 
 | ||||
| import pygame | ||||
| import time | ||||
| import sys | ||||
| import random | ||||
| 
 | ||||
| from world.world import World, Position | ||||
| from world.objects import DebugRenderObject, FoodObject, TestVelocityObject | ||||
| from world.world import World, Position, Rotation | ||||
| from world.objects import DebugRenderObject, FoodObject, TestVelocityObject, DefaultCell | ||||
| from world.simulation_interface import Camera | ||||
| 
 | ||||
| # Initialize Pygame | ||||
| @ -26,7 +28,7 @@ GRID_HEIGHT = 25  # Number of cells vertically | ||||
| CELL_SIZE = 20  # Size of each cell in pixels | ||||
| 
 | ||||
| DEFAULT_TPS = 20  # Number of ticks per second for the simulation | ||||
| FOOD_SPAWNING = False | ||||
| FOOD_SPAWNING = True | ||||
| 
 | ||||
| 
 | ||||
| def draw_grid(screen, camera, showing_grid=True): | ||||
| @ -107,6 +109,16 @@ def draw_grid(screen, camera, showing_grid=True): | ||||
|         for start, end in horizontal_lines: | ||||
|             pygame.draw.line(screen, GRAY, start, end) | ||||
| 
 | ||||
| def setup(world: World): | ||||
|     if FOOD_SPAWNING: | ||||
|         world.add_object(FoodObject(Position(x=random.randint(-100, 100), y=random.randint(-100, 100)))) | ||||
| 
 | ||||
|     world.add_object(TestVelocityObject(Position(x=random.randint(-100, 100), y=random.randint(-100, 100)))) | ||||
| 
 | ||||
|     world.add_object(DefaultCell(Position(x=0,y=0), Rotation(angle=0))) | ||||
| 
 | ||||
|     return world | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), vsync=1) | ||||
| @ -148,10 +160,7 @@ def main(): | ||||
|     # sets seed to 67 >_< | ||||
|     random.seed(0) | ||||
| 
 | ||||
|     if FOOD_SPAWNING: | ||||
|         world.add_object(FoodObject(Position(x=random.randint(-100, 100), y=random.randint(-100, 100)))) | ||||
| 
 | ||||
|     world.add_object(TestVelocityObject(Position(x=random.randint(-100, 100), y=random.randint(-100, 100)))) | ||||
|     world = setup(world) | ||||
| 
 | ||||
|     running = True | ||||
|     while running: | ||||
| @ -183,7 +192,7 @@ def main(): | ||||
|                 if event.key == pygame.K_SPACE: | ||||
|                     is_paused = not is_paused | ||||
|                 if event.key == pygame.K_LSHIFT: | ||||
|                     tps = DEFAULT_TPS * 2 | ||||
|                     tps = DEFAULT_TPS * 4 | ||||
|             elif event.type == pygame.KEYUP: | ||||
|                 if event.key == pygame.K_LSHIFT: | ||||
|                     tps = DEFAULT_TPS | ||||
| @ -251,8 +260,6 @@ def main(): | ||||
|                 objects = world.get_objects() | ||||
|                 food = len([obj for obj in objects if isinstance(obj, FoodObject)]) | ||||
| 
 | ||||
|                 # if food < 10 and FOOD_SPAWNING == True: | ||||
|                 #     world.add_object(FoodObject(Position(x=random.randint(-100, 100), y=random.randint(-100, 100)))) | ||||
| 
 | ||||
|                 # ensure selected objects are still valid or have not changed position, if so, reselect them | ||||
|                 selected_objects = [ | ||||
| @ -298,6 +305,133 @@ def main(): | ||||
|                             1  # 1 pixel thick | ||||
|                         ) | ||||
| 
 | ||||
|                         # Draw direction arrow | ||||
|                         rotation_angle = obj.rotation.get_rotation() | ||||
|                         arrow_length = obj.max_visual_width/2 * camera.zoom  # Scale arrow length with zoom | ||||
|                         arrow_color = (255, 255, 255)  # Green | ||||
| 
 | ||||
|                         # Calculate the arrow's end-point based on rotation angle | ||||
|                         end_x = screen_x + arrow_length * math.cos(math.radians(rotation_angle)) | ||||
|                         end_y = screen_y + arrow_length * math.sin(math.radians(rotation_angle)) | ||||
| 
 | ||||
|                         # Draw the arrow line | ||||
|                         pygame.draw.line(screen, arrow_color, (screen_x, screen_y), (end_x, end_y), 2) | ||||
| 
 | ||||
|                         # Draw a rotated triangle for the arrowhead | ||||
|                         tip_size = 3 * camera.zoom  # Scale triangle tip size with zoom | ||||
|                         left_tip_x = end_x - tip_size * math.cos(math.radians(rotation_angle + 150 + 180)) | ||||
|                         left_tip_y = end_y - tip_size * math.sin(math.radians(rotation_angle + 150 + 180)) | ||||
|                         right_tip_x = end_x - tip_size * math.cos(math.radians(rotation_angle - 150 + 180)) | ||||
|                         right_tip_y = end_y - tip_size * math.sin(math.radians(rotation_angle - 150 + 180)) | ||||
| 
 | ||||
|                         # Draw arrowhead (triangle) for direction | ||||
|                         pygame.draw.polygon( | ||||
|                             screen, | ||||
|                             arrow_color, | ||||
|                             [(end_x, end_y), (left_tip_x, left_tip_y), (right_tip_x, right_tip_y)] | ||||
|                         ) | ||||
| 
 | ||||
|                         # Draw angular acceleration arrow (if present) | ||||
|                         if hasattr(obj, 'angular_acceleration'): | ||||
|                             angular_acceleration = obj.angular_acceleration | ||||
| 
 | ||||
|                             # Scale the angular acceleration value for visibility | ||||
|                             angular_accel_magnitude = abs( | ||||
|                                 angular_acceleration) * 50 * camera.zoom  # Use absolute magnitude for scaling | ||||
| 
 | ||||
|                             # Determine the perpendicular direction based on the sign of angular_acceleration | ||||
|                             angular_direction = rotation_angle + 90 if angular_acceleration >= 0 else rotation_angle - 90 | ||||
| 
 | ||||
|                             # Calculate the end of the angular acceleration vector | ||||
|                             angular_acc_end_x = end_x + angular_accel_magnitude * math.cos( | ||||
|                                 math.radians(angular_direction)) | ||||
|                             angular_acc_end_y = end_y + angular_accel_magnitude * math.sin( | ||||
|                                 math.radians(angular_direction)) | ||||
| 
 | ||||
|                             # Draw the angular acceleration vector as a red line | ||||
|                             pygame.draw.line(screen, (52, 134, 235), (end_x, end_y), | ||||
|                                              (angular_acc_end_x, angular_acc_end_y), 2) | ||||
| 
 | ||||
|                             # Add an arrowhead to the angular acceleration vector | ||||
|                             angular_tip_size = 2.5 * camera.zoom | ||||
|                             left_angular_tip_x = angular_acc_end_x - angular_tip_size * math.cos( | ||||
|                                 math.radians(angular_direction + 150 + 180)) | ||||
|                             left_angular_tip_y = angular_acc_end_y - angular_tip_size * math.sin( | ||||
|                                 math.radians(angular_direction + 150 + 180)) | ||||
|                             right_angular_tip_x = angular_acc_end_x - angular_tip_size * math.cos( | ||||
|                                 math.radians(angular_direction - 150 + 180)) | ||||
|                             right_angular_tip_y = angular_acc_end_y - angular_tip_size * math.sin( | ||||
|                                 math.radians(angular_direction - 150 + 180)) | ||||
| 
 | ||||
|                             # Draw arrowhead (triangle) for angular acceleration | ||||
|                             pygame.draw.polygon( | ||||
|                                 screen, | ||||
|                                 (52, 134, 235),  # Red arrowhead | ||||
|                                 [(angular_acc_end_x, angular_acc_end_y), (left_angular_tip_x, left_angular_tip_y), | ||||
|                                  (right_angular_tip_x, right_angular_tip_y)] | ||||
|                             ) | ||||
| 
 | ||||
|                         # If object has an acceleration attribute, draw a red vector with arrowhead | ||||
|                         if hasattr(obj, 'acceleration') and isinstance(obj.acceleration, tuple) and len( | ||||
|                                 obj.acceleration) == 2: | ||||
|                             acc_x, acc_y = obj.acceleration | ||||
| 
 | ||||
|                             # Calculate acceleration magnitude and direction | ||||
|                             acc_magnitude = math.sqrt(acc_x ** 2 + acc_y ** 2) | ||||
|                             if acc_magnitude > 0: | ||||
|                                 acc_direction = math.degrees(math.atan2(acc_y, acc_x))  # Get the angle in degrees | ||||
| 
 | ||||
|                                 # Calculate scaled acceleration vector's end point | ||||
|                                 acc_vector_length = acc_magnitude * 1000 * camera.zoom  # Scale length with zoom | ||||
|                                 acc_end_x = screen_x + acc_vector_length * math.cos(math.radians(acc_direction)) | ||||
|                                 acc_end_y = screen_y + acc_vector_length * math.sin(math.radians(acc_direction)) | ||||
| 
 | ||||
|                                 # Draw the acceleration vector as a red line | ||||
|                                 pygame.draw.line(screen, (255, 0, 0), (screen_x, screen_y), (acc_end_x, acc_end_y), 2) | ||||
| 
 | ||||
|                                 # Add arrowhead to acceleration vector | ||||
|                                 acc_tip_size = 5 * camera.zoom | ||||
|                                 left_tip_x = acc_end_x - acc_tip_size * math.cos(math.radians(acc_direction + 150 + 180)) | ||||
|                                 left_tip_y = acc_end_y - acc_tip_size * math.sin(math.radians(acc_direction + 150 + 180)) | ||||
|                                 right_tip_x = acc_end_x - acc_tip_size * math.cos(math.radians(acc_direction - 150 + 180)) | ||||
|                                 right_tip_y = acc_end_y - acc_tip_size * math.sin(math.radians(acc_direction - 150 + 180)) | ||||
| 
 | ||||
|                                 pygame.draw.polygon( | ||||
|                                     screen, | ||||
|                                     (255, 0, 0),  # Red arrowhead | ||||
|                                     [(acc_end_x, acc_end_y), (left_tip_x, left_tip_y), (right_tip_x, right_tip_y)] | ||||
|                                 ) | ||||
| 
 | ||||
|                         # If object has a velocity attribute, draw a blue vector with arrowhead | ||||
|                         if hasattr(obj, 'velocity') and isinstance(obj.velocity, tuple) and len(obj.velocity) == 2: | ||||
|                             vel_x, vel_y = obj.velocity | ||||
| 
 | ||||
|                             # Calculate velocity magnitude and direction | ||||
|                             vel_magnitude = math.sqrt(vel_x ** 2 + vel_y ** 2) | ||||
|                             if vel_magnitude > 0: | ||||
|                                 vel_direction = math.degrees(math.atan2(vel_y, vel_x))  # Get the angle in degrees | ||||
| 
 | ||||
|                                 # Calculate scaled velocity vector's end point | ||||
|                                 vel_vector_length = vel_magnitude * 50 * camera.zoom  # Scale length with zoom | ||||
|                                 vel_end_x = screen_x + vel_vector_length * math.cos(math.radians(vel_direction)) | ||||
|                                 vel_end_y = screen_y + vel_vector_length * math.sin(math.radians(vel_direction)) | ||||
| 
 | ||||
|                                 # Draw the velocity vector as a blue line | ||||
|                                 pygame.draw.line(screen, (0, 0, 255), (screen_x, screen_y), (vel_end_x, vel_end_y), 2) | ||||
| 
 | ||||
|                                 # Add arrowhead to velocity vector | ||||
|                                 vel_tip_size = 5 * camera.zoom | ||||
|                                 left_tip_x = vel_end_x - vel_tip_size * math.cos(math.radians(vel_direction + 150 + 180)) | ||||
|                                 left_tip_y = vel_end_y - vel_tip_size * math.sin(math.radians(vel_direction + 150 + 180)) | ||||
|                                 right_tip_x = vel_end_x - vel_tip_size * math.cos(math.radians(vel_direction - 150 + 180)) | ||||
|                                 right_tip_y = vel_end_y - vel_tip_size * math.sin(math.radians(vel_direction - 150 + 180)) | ||||
| 
 | ||||
|                                 pygame.draw.polygon( | ||||
|                                     screen, | ||||
|                                     (0, 0, 255),  # Blue arrowhead | ||||
|                                     [(vel_end_x, vel_end_y), (left_tip_x, left_tip_y), (right_tip_x, right_tip_y)] | ||||
|                                 ) | ||||
| 
 | ||||
|         # Draw selection rectangle if selecting | ||||
|         if selecting and select_start and select_end: | ||||
|             rect_color = (128, 128, 128, 80)  # Gray, semi-transparent | ||||
| @ -353,14 +487,37 @@ def main(): | ||||
| 
 | ||||
|         if len(selected_objects) >= 1: | ||||
|             i = 0 | ||||
|             max_width = SCREEN_WIDTH - 20  # Leave some padding from the right edge | ||||
|             for each in selected_objects: | ||||
|                 obj = each | ||||
|                 obj_text = font.render( | ||||
|                     f"Object: {str(obj)}", True, WHITE | ||||
|                 ) | ||||
|                 obj_rect = obj_text.get_rect() | ||||
|                 obj_rect.topleft = (10, 30 + i * 20) | ||||
|                 screen.blit(obj_text, obj_rect) | ||||
|                 text = f"Object: {str(obj)}" | ||||
|                 words = text.split()  # Split text into words | ||||
|                 line = "" | ||||
|                 line_height = 20  # Height of each line of text | ||||
|                 line_offset = 0 | ||||
| 
 | ||||
|                 for word in words: | ||||
|                     test_line = f"{line} {word}".strip() | ||||
|                     test_width, _ = font.size(test_line) | ||||
| 
 | ||||
|                     # Check if the line width exceeds the limit | ||||
|                     if test_width > max_width and line: | ||||
|                         obj_text = font.render(line, True, WHITE) | ||||
|                         obj_rect = obj_text.get_rect() | ||||
|                         obj_rect.topleft = (10, 30 + i * line_height + line_offset) | ||||
|                         screen.blit(obj_text, obj_rect) | ||||
|                         line = word  # Start a new line | ||||
|                         line_offset += line_height | ||||
|                     else: | ||||
|                         line = test_line | ||||
| 
 | ||||
|                 # Render the last line | ||||
|                 if line: | ||||
|                     obj_text = font.render(line, True, WHITE) | ||||
|                     obj_rect = obj_text.get_rect() | ||||
|                     obj_rect.topleft = (10, 30 + i * line_height + line_offset) | ||||
|                     screen.blit(obj_text, obj_rect) | ||||
| 
 | ||||
|                 i += 1 | ||||
| 
 | ||||
|         legend_font = pygame.font.Font("freesansbold.ttf", 14) | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| from world.behavorial import BehavioralModel | ||||
| from world.behavioral import BehavioralModel | ||||
| 
 | ||||
| 
 | ||||
| class TestBrain(BehavioralModel): | ||||
| class CellBrain(BehavioralModel): | ||||
|     def __init__(self): | ||||
|         super().__init__() | ||||
|         # Define input keys | ||||
| @ -12,13 +12,13 @@ class TestBrain(BehavioralModel): | ||||
| 
 | ||||
|         # Define output keys | ||||
|         self.outputs = { | ||||
|             'acceleration': 0.0,  # Linear acceleration | ||||
|             'linear_acceleration': 0.0,  # Linear acceleration | ||||
|             'angular_acceleration': 0.0  # Angular acceleration | ||||
|         } | ||||
| 
 | ||||
|         self.weights = { | ||||
|             'distance': 1.0, | ||||
|             'angle': 1.0 | ||||
|             'distance': 1, | ||||
|             'angle': 0.5 | ||||
|         } | ||||
| 
 | ||||
|     def tick(self, input_data) -> dict: | ||||
| @ -26,16 +26,22 @@ class TestBrain(BehavioralModel): | ||||
|         Process inputs and produce corresponding outputs. | ||||
| 
 | ||||
|         :param input_data: Dictionary containing 'distance' and 'angle' values | ||||
|         :return: Dictionary with 'acceleration' and 'angular_acceleration' values | ||||
|         :return: Dictionary with 'linear_acceleration' and 'angular_acceleration' values | ||||
|         """ | ||||
|         # Update internal input state | ||||
|         self.inputs['distance'] = input_data.get('distance', 0.0) | ||||
|         self.inputs['angle'] = input_data.get('angle', 0.0) | ||||
| 
 | ||||
|         # Initialize output dictionary | ||||
|         output_data = { | ||||
|             'acceleration': 0.0, | ||||
|             'angular_acceleration': 0.0 | ||||
|         } | ||||
|         output_data = {'linear_acceleration': self.inputs['distance'] * self.weights['distance'], | ||||
| 					   'angular_acceleration': self.inputs['angle'] * self.weights['angle']} | ||||
| 
 | ||||
|         self.outputs = output_data | ||||
| 
 | ||||
|         return output_data | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         inputs = {key: round(value, 1) for key, value in self.inputs.items()} | ||||
|         outputs = {key: round(value, 1) for key, value in self.outputs.items()} | ||||
|         weights = {key: round(value, 1) for key, value in self.weights.items()} | ||||
|         return f"CellBrain(inputs={inputs}, outputs={outputs}, weights={weights})" | ||||
|  | ||||
| @ -24,4 +24,8 @@ class BehavioralModel: | ||||
|         """ | ||||
|         output_data = {} | ||||
| 
 | ||||
|         for key in self.outputs: | ||||
|             if key not in output_data: | ||||
|                 raise KeyError(f"Output key '{key}' not found in output data.") | ||||
| 
 | ||||
|         return output_data | ||||
							
								
								
									
										178
									
								
								world/objects.py
									
									
									
									
									
								
							
							
						
						
									
										178
									
								
								world/objects.py
									
									
									
									
									
								
							| @ -1,8 +1,15 @@ | ||||
| import math | ||||
| import random | ||||
| 
 | ||||
| from world.world import Position, BaseEntity | ||||
| from world.base.brain import CellBrain | ||||
| from world.behavioral import BehavioralModel | ||||
| from world.world import Position, BaseEntity, Rotation | ||||
| import pygame | ||||
| from typing import Optional, List, Any | ||||
| from typing import Optional, List, Any, Union | ||||
| 
 | ||||
| from world.utils import get_distance_between_objects | ||||
| 
 | ||||
| from math import atan2, degrees | ||||
| 
 | ||||
| 
 | ||||
| class DebugRenderObject(BaseEntity): | ||||
| @ -17,7 +24,7 @@ class DebugRenderObject(BaseEntity): | ||||
|         :param position: The position of the object. | ||||
|         :param radius: The radius of the rendered circle. | ||||
|         """ | ||||
|         super().__init__(position) | ||||
|         super().__init__(position, Rotation(angle=0)) | ||||
|         self.neighbors: int = 0 | ||||
|         self.radius: int = radius | ||||
|         self.max_visual_width: int = radius * 2 | ||||
| @ -93,7 +100,7 @@ class FoodObject(BaseEntity): | ||||
| 
 | ||||
|         :param position: The position of the food. | ||||
|         """ | ||||
|         super().__init__(position) | ||||
|         super().__init__(position, Rotation(angle=0)) | ||||
|         self.max_visual_width: int = 10 | ||||
|         self.decay: int = 0 | ||||
|         self.decay_rate: int = 1 | ||||
| @ -105,7 +112,7 @@ class FoodObject(BaseEntity): | ||||
|             "can_interact": True, | ||||
|         } | ||||
| 
 | ||||
|     def tick(self, interactable: Optional[List[BaseEntity]] = None) -> Optional["FoodObject"]: | ||||
|     def tick(self, interactable: Optional[List[BaseEntity]] = None) -> Union["FoodObject", List["FoodObject"]]: | ||||
|         """ | ||||
|         Updates the food object, increasing decay and flagging for death if decayed. | ||||
| 
 | ||||
| @ -183,7 +190,7 @@ class TestVelocityObject(BaseEntity): | ||||
| 
 | ||||
|         :param position: The position of the object. | ||||
|         """ | ||||
|         super().__init__(position) | ||||
|         super().__init__(position, Rotation(angle=random.randint(0, 360))) | ||||
|         self.velocity = (random.uniform(-0.1, 0.5), random.uniform(-0.1, 0.5)) | ||||
|         self.max_visual_width: int = 10 | ||||
|         self.interaction_radius: int = 50 | ||||
| @ -237,3 +244,162 @@ class DefaultCell(BaseEntity): | ||||
|     """ | ||||
|     Cell object | ||||
|     """ | ||||
|     def __init__(self, starting_position: Position, starting_rotation: Rotation) -> None: | ||||
|         """ | ||||
|         Initializes the cell. | ||||
| 
 | ||||
|         :param starting_position: The position of the object. | ||||
|         """ | ||||
| 
 | ||||
|         super().__init__(starting_position, starting_rotation) | ||||
|         self.drag_coefficient: float = 0.1 | ||||
| 
 | ||||
|         self.velocity: tuple[int, int] = (0, 0) | ||||
|         self.acceleration: tuple[int, int] = (0, 0) | ||||
| 
 | ||||
|         self.rotational_velocity: int = 0 | ||||
|         self.angular_acceleration: int = 0 | ||||
| 
 | ||||
|         self.behavioral_model: CellBrain = CellBrain() | ||||
| 
 | ||||
|         self.max_visual_width: int = 10 | ||||
|         self.interaction_radius: int = 50 | ||||
|         self.flags: dict[str, bool] = { | ||||
|             "death": False, | ||||
|             "can_interact": True, | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     def set_brain(self, behavioral_model: CellBrain) -> None: | ||||
|         self.behavioral_model = behavioral_model | ||||
| 
 | ||||
| 
 | ||||
|     def tick(self, interactable: Optional[List[BaseEntity]] = None) -> "DefaultCell": | ||||
|         """ | ||||
|         Updates the cell according to its behavioral model. | ||||
| 
 | ||||
|         :param interactable: List of nearby entities (unused). | ||||
|         :return: Self. | ||||
|         """ | ||||
|         if interactable is None: | ||||
|             interactable = [] | ||||
| 
 | ||||
|         # filter interactable objects | ||||
|         food_objects = self.filter_food(interactable) | ||||
| 
 | ||||
|         # grab the closest food | ||||
|         if len(food_objects) > 0: | ||||
|             food_object = food_objects[0] | ||||
|         else: | ||||
|             food_object = FoodObject(self.position) | ||||
| 
 | ||||
|         angle_between_food = self.calculate_angle_between_food(self.position.get_position(), self.rotation.get_rotation(), food_object.position.get_position()) | ||||
| 
 | ||||
|         input_data = { | ||||
|             "distance": get_distance_between_objects(self, food_object), | ||||
|             "angle": angle_between_food, | ||||
|         } | ||||
| 
 | ||||
|         output_data = self.behavioral_model.tick(input_data) | ||||
| 
 | ||||
|         # clamp accelerations | ||||
|         output_data["linear_acceleration"] = max(-0.1, min(0.02, output_data["linear_acceleration"])) | ||||
|         output_data["angular_acceleration"] = max(-0.1, min(0.1, output_data["angular_acceleration"])) | ||||
| 
 | ||||
|         # output acceleration is acceleration along its current rotation. | ||||
|         x_component = output_data["linear_acceleration"] * math.cos(math.radians(self.rotation.get_rotation())) | ||||
|         y_component = output_data["linear_acceleration"] * math.sin(math.radians(self.rotation.get_rotation())) | ||||
| 
 | ||||
|         self.acceleration = (x_component, y_component) | ||||
|          | ||||
|         # # add drag according to current velocity | ||||
|         # drag_coefficient = 0.3 | ||||
|         # drag_x = -self.velocity[0] * drag_coefficient | ||||
|         # drag_y = -self.velocity[1] * drag_coefficient | ||||
|         # self.acceleration = (self.acceleration[0] + drag_x, self.acceleration[1] + drag_y) | ||||
| 
 | ||||
|         # 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 | ||||
|         self.velocity = (max(-0.5, min(0.5, self.velocity[0])), max(-0.5, min(0.5, self.velocity[1]))) | ||||
| 
 | ||||
|         # tick velocity | ||||
|         x, y = self.position.get_position() | ||||
|         x += self.velocity[0] | ||||
|         y += self.velocity[1] | ||||
| 
 | ||||
|         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(-0.5, min(0.5, self.rotational_velocity)) | ||||
| 
 | ||||
|         # tick rotational velocity | ||||
|         self.rotation.set_rotation(self.rotation.get_rotation() + self.rotational_velocity) | ||||
| 
 | ||||
|         return self | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def calculate_angle_between_food(object_position, object_rotation, food_position) -> float: | ||||
|         """ | ||||
| 		Calculates the angle between an object's current rotation and the position of the food. | ||||
| 
 | ||||
| 		:param object_position: Tuple of (x, y) for the object's position. | ||||
| 		:param object_rotation: Current rotation of the object in degrees. | ||||
| 		:param food_position: Tuple of (x, y) for the food's position. | ||||
| 		:return: Angle between -180 and 180 degrees. | ||||
| 		""" | ||||
|         obj_x, obj_y = object_position | ||||
|         food_x, food_y = food_position | ||||
| 
 | ||||
|         # Calculate the angle to the food relative to the object | ||||
|         angle_to_food = math.degrees(math.atan2(food_y - obj_y, food_x - obj_x)) | ||||
| 
 | ||||
|         # Calculate the relative angle to the object's rotation | ||||
|         angle_between = angle_to_food - object_rotation | ||||
| 
 | ||||
|         # Normalize the angle to be between -180 and 180 degrees | ||||
|         if angle_between > 180: | ||||
|             angle_between -= 360 | ||||
|         elif angle_between < -180: | ||||
|             angle_between += 360 | ||||
| 
 | ||||
|         return angle_between | ||||
| 
 | ||||
|     def filter_food(self, input_objects: List[BaseEntity]) -> List[FoodObject]: | ||||
|         """ | ||||
|         Filters the input objects to only include food. Sort output by distance, closest first | ||||
|         """ | ||||
|         food_objects = [] | ||||
|         for obj in input_objects: | ||||
|             if isinstance(obj, FoodObject): | ||||
|                 food_objects.append(obj) | ||||
|         food_objects.sort(key=lambda x: get_distance_between_objects(self, x)) | ||||
|         return food_objects | ||||
| 
 | ||||
|     def render(self, camera: Any, screen: Any) -> None: | ||||
|         """ | ||||
|         Renders the cell as a circle. | ||||
| 
 | ||||
|         :param camera: The camera object for coordinate transformation. | ||||
|         :param screen: The Pygame screen surface. | ||||
|         """ | ||||
|         if camera.is_in_view(*self.position.get_position()): | ||||
|             pygame.draw.circle( | ||||
|                 screen, | ||||
|                 (0, 255, 0), | ||||
|                 camera.world_to_screen(*self.position.get_position()), | ||||
|                 int(5 * camera.zoom) | ||||
|             ) | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         position = f"({round(self.position.x, 1)}, {round(self.position.y, 1)})" | ||||
|         velocity = tuple(round(value, 1) for value in self.velocity) | ||||
|         acceleration = tuple(round(value, 1) for value in self.acceleration) | ||||
|         return f"DefaultCell(position={position}, velocity={velocity}, acceleration={acceleration}, behavioral_model={self.behavioral_model})" | ||||
|  | ||||
							
								
								
									
										2
									
								
								world/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								world/utils.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| def get_distance_between_objects(object_a, object_b): | ||||
|     return ((object_a.position.x - object_b.position.x)**2 + (object_a.position.y - object_b.position.y)**2)**0.5 | ||||
| @ -36,6 +36,35 @@ class Position(BaseModel): | ||||
|         :return: Tuple of (x, y). | ||||
|         """ | ||||
|         return self.x, self.y | ||||
|      | ||||
|      | ||||
| class Rotation(BaseModel): | ||||
|     """ | ||||
|     Represents rotation in degrees (0-360). | ||||
|     """ | ||||
|     angle: float = Field(..., description="Rotation angle in degrees", ge=0, lt=360) | ||||
| 
 | ||||
|     def __str__(self) -> str: | ||||
|         return f"{self.angle}°" | ||||
| 
 | ||||
|     def __repr__(self) -> str: | ||||
|         return f"Rotation({self.angle})" | ||||
| 
 | ||||
|     def set_rotation(self, angle: float) -> None: | ||||
|         """ | ||||
|         Sets the rotation angle. | ||||
| 
 | ||||
|         :param angle: New angle in degrees (0-360). | ||||
|         """ | ||||
|         self.angle = angle % 360 | ||||
| 
 | ||||
|     def get_rotation(self) -> float: | ||||
|         """ | ||||
|         Returns the current rotation angle. | ||||
| 
 | ||||
|         :return: Angle in degrees. | ||||
|         """ | ||||
|         return self.angle | ||||
| 
 | ||||
| 
 | ||||
| class BaseEntity(ABC): | ||||
| @ -43,13 +72,14 @@ class BaseEntity(ABC): | ||||
|     Abstract base class for all entities in the world. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, position: Position) -> None: | ||||
|     def __init__(self, position: Position, rotation: Rotation) -> None: | ||||
|         """ | ||||
|         Initializes the entity with a position. | ||||
|         Initializes the entity with a position and rotation. | ||||
| 
 | ||||
|         :param position: The position of the entity. | ||||
|         """ | ||||
|         self.position: Position = position | ||||
|         self.rotation: Rotation = rotation | ||||
|         self.interaction_radius: int = 0 | ||||
|         self.flags: Dict[str, bool] = { | ||||
|             "death": False, | ||||
| @ -252,4 +282,4 @@ class World: | ||||
|         all_objects: List[BaseEntity] = [] | ||||
|         for obj_list in self.buffers[self.current_buffer].values(): | ||||
|             all_objects.extend(obj_list) | ||||
|         return all_objects | ||||
|         return all_objects | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user