Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: refactor Hive class to inherit from Entity class #1

Merged
merged 2 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions src/ninjabees/Animator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ def clear_terminal():
os.system('clear' if os.name == 'posix' else 'cls')

@staticmethod
def print_hive_status(hive):
def print_world_status(world_map, found, total):
Animator.clear_terminal()
for row in hive.map:
for row in world_map:
print(''.join(row))
print(f'Found Food Sources: {len(hive.found_food_sources)} of {len(hive.food_sources)}')

hive.map = [['-' for _ in range(200)] for _ in range(90)]
hive.map[hive.x][hive.y] = 'H'
print(f'Found Food Sources: {found} of {total}')
time.sleep(0.09)
90 changes: 52 additions & 38 deletions src/ninjabees/Bee.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import random

from enum import Enum

from .environment.Entity import Entity, EntityType


class BeeJob(Enum):
Scout = 0
Forager = 1


class Bee:
def __init__(self, name, hive, exploration_radius=1, move_range=5):
class Bee(Entity):
def __init__(self, name, hive, world, exploration_radius=1, move_range=5):
super().__init__(hive.get_x(), hive.get_y(), EntityType.Bee)

self.name = name
self.hive = hive
self.x = self.hive.get_x()
self.y = self.hive.get_y()
self.world = world

# Initially no food source is known
self.__job = BeeJob.Scout
Expand Down Expand Up @@ -43,11 +45,11 @@ def set_job(self, job):
def set_food_goal(self, food_goal):
self.__food_goal = food_goal

def explore(self, food_sources):
def explore(self):
if self.__food_goal and self.__job != BeeJob.Scout:
self.move_towards_exploration_goal()
return
nearby_sources = [source for source in food_sources if self.is_within_radius(source)]
nearby_sources = [source for source in self.world.get_unclaimed_food_sources() if self.is_within_radius(source)]
if nearby_sources:
selected_source = random.choice(nearby_sources)
self.__found_food = True
Expand All @@ -56,54 +58,66 @@ def explore(self, food_sources):
self.move()

def is_within_radius(self, food_source):
distance = ((self.x - food_source.x) ** 2 + (self.y - food_source.y) ** 2) ** 0.5
distance = ((self.get_x() - food_source.get_x()) ** 2 + (self.get_y() - food_source.get_y()) ** 2) ** 0.5
return distance <= self.__exploration_radius

def return_home(self):
if self.x == self.hive.get_x() and self.y == self.hive.get_y():
if self.__found_food_source not in self.hive.found_food_sources:
x = self.get_x()
y = self.get_y()

if x == self.hive.get_x() and y == self.hive.get_y():
if self.__found_food_source not in self.hive.food_sources:
self.hive.add_found_food_source(self.__found_food_source)
self.__found_food = False
self.__found_food_source = None
self.__food_goal = None
else:
if self.x != self.hive.get_x():
if (self.x - self.hive.x) > 0:
self.x -= 1
if x != self.hive.get_x():
if (x - self.hive.get_x()) > 0:
self.set_x(x - 1)
else:
self.x += 1
if self.y != self.hive.get_y():
if (self.y - self.hive.y) > 0:
self.y -= 1
self.set_x(x + 1)
if y != self.hive.get_y():
if (y - self.hive.get_y()) > 0:
self.set_y(y - 1)
else:
self.y += 1
self.set_y(y + 1)

def move_towards_exploration_goal(self):
if self.x == self.__food_goal.x and self.y == self.__food_goal.y:
x = self.get_x()
y = self.get_y()

if x == self.__food_goal.get_x() and y == self.__food_goal.get_y():
self.__found_food = True
self.__found_food_source = self.__food_goal
self.__food_goal = None
else:
if self.x != self.__food_goal.x:
if (self.x - self.__food_goal.x) > 0:
self.x -= 1
if x != self.__food_goal.get_x():
if (x - self.__food_goal.get_x()) > 0:
self.set_x(x - 1)
else:
self.x += 1
if self.y != self.__food_goal.y:
if (self.y - self.__food_goal.y) > 0:
self.y -= 1
self.set_x(x + 1)
if y != self.__food_goal.get_y():
if (y - self.__food_goal.get_y()) > 0:
self.set_y(y - 1)
else:
self.y += 1
self.set_y(y + 1)

def move(self):
self.x += int(random.uniform(-self.__move_range, self.__move_range))
self.y += int(random.uniform(-self.__move_range, self.__move_range))

if self.x < 0:
self.x = 0
if self.y < 0:
self.y = 0
if self.x > 89:
self.x = 89
if self.y > 199:
self.y = 199
x = self.get_x()
y = self.get_y()

x += int(random.uniform(-self.__move_range, self.__move_range))
y += int(random.uniform(-self.__move_range, self.__move_range))

self.set_x(x)
self.set_y(y)

if x < 0:
self.set_x(0)
if y < 0:
self.set_y(0)
if x > 89:
self.set_x(89)
if y > 199:
self.set_y(199)
18 changes: 5 additions & 13 deletions src/ninjabees/FoodSource.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
class FoodSource:
from .environment.Entity import Entity, EntityType


class FoodSource(Entity):
def __init__(self, name, nutritional_val, x, y):
self.name = name
super().__init__(x, y, EntityType.Food)
self.nutritional_val = nutritional_val
self.amount = 100
self.x = x
self.y = y

def get_amount(self):
return self.amount

def get_name(self):
return self.name

def get_x(self):
return self.x

def get_y(self):
return self.y
65 changes: 17 additions & 48 deletions src/ninjabees/Hive.py
Original file line number Diff line number Diff line change
@@ -1,80 +1,49 @@
import random

from .Animator import Animator
from .Bee import Bee, BeeJob
from .environment.Entity import Entity, EntityType


class Hive:
def __init__(self, name, num_onlooker_bees, max_cnt_foraging_bees=100, x=0, y=0):
class Hive(Entity):
def __init__(self, name, num_onlooker_bees, max_cnt_foraging_bees=100, x=0, y=0, world=None):
super().__init__(x, y, EntityType.Hive)

self.name = name
self.num_onlooker_bees = num_onlooker_bees
self.world = world

self.food_sources = []
self.found_food_sources = []

self.x = x
self.y = y

self.__current_foraging = 0
self.max_cnt_foraging_bees = max_cnt_foraging_bees
self.bee_population = [Bee("Bee", self) for _ in range(200)]

self.map = [['-' for _ in range(200)] for _ in range(90)]
self.map[self.x][self.y] = 'H'

Animator.print_hive_status(self)

def get_x(self):
return self.x

def get_y(self):
return self.y

def add_food_source(self, food_source):
while food_source.x == self.x and food_source.y == self.y:
food_source.x = int(random.uniform(0, 89))
food_source.y = int(random.uniform(0, 199))
self.food_sources.append(food_source)
self.bee_population = [Bee("Bee", self, world) for _ in range(200)]

def add_found_food_source(self, food_source):
self.found_food_sources.append(food_source)

def calculate_food_source_quality(self, food_source):
# Calculate the quality of a food source based on its distance from the hive and the nutritional_val of the food
distance = ((self.x - food_source.x) ** 2 + (self.y - food_source.y) ** 2) ** 0.5
distance = ((self.get_x() - food_source.get_x()) ** 2 + (self.get_x() - food_source.get_y()) ** 2) ** 0.5
return food_source.nutritional_val / distance

def forage(self, max_iterations):
for iteration in range(max_iterations):
self.employed_bees_phase()
self.onlooker_bees_phase()

# Update the map
for bee in self.bee_population:
if bee.x != 0 or bee.y != 0:
if bee.has_found_food():
self.map[bee.x][bee.y] = 'S' if bee.get_job() == BeeJob.Scout else 'B'
else:
self.map[bee.x][bee.y] = 's' if bee.get_job() == BeeJob.Scout else 'b'
for source in self.found_food_sources:
if source.x != 0 or source.y != 0:
self.map[source.x][source.y] = 'F'

Animator.print_hive_status(self)

if len(self.found_food_sources) == len(self.food_sources):
print(f'All food sources found! In {iteration} iterations')
return
def forage(self):
self.employed_bees_phase()
self.onlooker_bees_phase()
for food_source in self.found_food_sources:
if food_source not in self.food_sources:
self.world.add_entity(food_source)
self.food_sources.append(food_source)

def employed_bees_phase(self):
for bee in self.bee_population:
if bee.has_found_food():
bee.return_home()
else:
bee.explore(self.food_sources)
bee.explore()

def onlooker_bees_phase(self):
if len(self.found_food_sources) == 0:
if len(self.food_sources) == 0:
return

for bee in self.bee_population:
Expand Down
30 changes: 30 additions & 0 deletions src/ninjabees/environment/Entity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from enum import Enum


class EntityType(Enum):
Bee = 0
Hive = 1
Food = 2


class Entity:

def __init__(self, x, y, type):
self.__x = x
self.__y = y
self.__type = type

def get_x(self):
return self.__x

def get_y(self):
return self.__y

def get_type(self):
return self.__type

def set_x(self, x):
self.__x = x

def set_y(self, y):
self.__y = y
70 changes: 70 additions & 0 deletions src/ninjabees/environment/World.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from ..Animator import Animator
from ..Bee import BeeJob
from .Entity import EntityType


class World:
def __init__(self, width, height):
self.__width = width
self.__height = height
self.__entities = []

self.__hive = None
self.__food_sources = []

self.__unclaimed_food_sources = []
self.__n_fod_sources = 0

self.__world_map = [['-' for _ in range(width)] for _ in range(height)]

def add_food_source(self, food_source):
self.__unclaimed_food_sources.append(food_source)
self.__n_fod_sources += 1

def get_unclaimed_food_sources(self):
return self.__unclaimed_food_sources

def add_entity(self, entity):
self.__entities.append(entity)
if entity.get_type() == EntityType.Hive:
self.__hive = entity
elif entity.get_type() == EntityType.Food:
if entity not in self.__food_sources:
self.__food_sources.append(entity)

def is_position_blocked(self, x, y):
for entity in self.__entities:
if entity.x == x and entity.y == y:
return True
return False

def update_world_map(self):
for entity in self.__entities:
entity_type = entity.get_type()
entity_x = entity.get_x()
entity_y = entity.get_y()
if entity_type == EntityType.Bee:
if entity.has_found_food():
self.__world_map[entity_x][entity_y] = 'S' if entity.get_job() == BeeJob.Scout else 'B'
else:
self.__world_map[entity_x][entity_y] = 's' if entity.get_job() == BeeJob.Scout else 'b'
elif entity_type == EntityType.Food:
self.__world_map[entity_x][entity_y] = 'F'
elif entity_type == EntityType.Hive:
self.__world_map[entity_x][entity_y] = 'H'
else:
raise ValueError("Unknown entity type")

def run(self, max_iterations):
for iteration in range(max_iterations):
self.__hive.forage()

self.update_world_map()
Animator.print_world_status(world_map=self.__world_map, found=len(self.__hive.found_food_sources),
total=self.__n_fod_sources)

self.__world_map = [['-' for _ in range(self.__width)] for _ in range(self.__height)]

if len(self.__hive.found_food_sources) == self.__n_fod_sources:
print(f'All food sources found! In {iteration} iterations')
return
Empty file.
Loading
Loading