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
185
main.py
185
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
|
||||
)
|
||||
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 * 20)
|
||||
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
|
||||
@ -38,18 +38,48 @@ class Position(BaseModel):
|
||||
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):
|
||||
"""
|
||||
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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user