diff --git a/src/ninjabees/Animator.py b/src/ninjabees/Animator.py index be8b9f9..dca77dd 100644 --- a/src/ninjabees/Animator.py +++ b/src/ninjabees/Animator.py @@ -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) diff --git a/src/ninjabees/Bee.py b/src/ninjabees/Bee.py index 494bdcd..b73afb6 100644 --- a/src/ninjabees/Bee.py +++ b/src/ninjabees/Bee.py @@ -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 @@ -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 @@ -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) diff --git a/src/ninjabees/FoodSource.py b/src/ninjabees/FoodSource.py index a0bcfb0..2ca54fb 100644 --- a/src/ninjabees/FoodSource.py +++ b/src/ninjabees/FoodSource.py @@ -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 diff --git a/src/ninjabees/Hive.py b/src/ninjabees/Hive.py index ce969d1..29d9013 100644 --- a/src/ninjabees/Hive.py +++ b/src/ninjabees/Hive.py @@ -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: diff --git a/src/ninjabees/environment/Entity.py b/src/ninjabees/environment/Entity.py new file mode 100644 index 0000000..f03e446 --- /dev/null +++ b/src/ninjabees/environment/Entity.py @@ -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 diff --git a/src/ninjabees/environment/World.py b/src/ninjabees/environment/World.py new file mode 100644 index 0000000..37772dc --- /dev/null +++ b/src/ninjabees/environment/World.py @@ -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 diff --git a/src/ninjabees/environment/__init__.py b/src/ninjabees/environment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/ninjabees/main.py b/src/ninjabees/main.py index 36a1f6c..f09ff9f 100644 --- a/src/ninjabees/main.py +++ b/src/ninjabees/main.py @@ -2,6 +2,7 @@ import sys import random +from .environment.World import World from .Hive import Hive from .FoodSource import FoodSource @@ -35,9 +36,6 @@ def parse_args(args): def main(args): args = parse_args(args) - # Example usage - hive = Hive("MyHive", num_onlooker_bees=1, x=int(random.uniform(0, 89)), y=int(random.uniform(0, 199))) - food1 = FoodSource("Flower", 80, int(random.uniform(0, 89)), int(random.uniform(0, 199))) food2 = FoodSource("Tree", 10, int(random.uniform(0, 89)), int(random.uniform(0, 199))) food3 = FoodSource("Garden", 100, int(random.uniform(0, 89)), int(random.uniform(0, 199))) @@ -45,14 +43,21 @@ def main(args): food5 = FoodSource("Tree 2", 77, int(random.uniform(0, 89)), int(random.uniform(0, 199))) food6 = FoodSource("Garden 2", 300, int(random.uniform(0, 89)), int(random.uniform(0, 199))) - hive.add_food_source(food1) - hive.add_food_source(food2) - hive.add_food_source(food3) - hive.add_food_source(food4) - hive.add_food_source(food5) - hive.add_food_source(food6) + world = World(200, 90) + + world.add_food_source(food1) + world.add_food_source(food2) + world.add_food_source(food3) + world.add_food_source(food4) + world.add_food_source(food5) + world.add_food_source(food6) + + hive = Hive("MyHive", num_onlooker_bees=1, x=int(random.uniform(0, 89)), y=int(random.uniform(0, 199)), world=world) + world.add_entity(hive) + for bee in hive.bee_population: + world.add_entity(bee) - hive.forage(max_iterations=10000) + world.run(1000000) def run():