From 3491a98c5034da041075ff0795463e00bc458a8d Mon Sep 17 00:00:00 2001 From: CaedenPH Date: Fri, 28 Jul 2023 18:58:34 +0300 Subject: [PATCH 01/21] feat(cellular_automata): Create wa-tor algorithm --- cellular_automata/wa_tor.py | 587 ++++++++++++++++++++++++++++++++++++ 1 file changed, 587 insertions(+) create mode 100644 cellular_automata/wa_tor.py diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py new file mode 100644 index 000000000000..f9d19e363442 --- /dev/null +++ b/cellular_automata/wa_tor.py @@ -0,0 +1,587 @@ +""" +Wa-Tor algorithm (1984) + +@ https://en.wikipedia.org/wiki/Wa-Tor +@ https://beltoforion.de/en/wator/ +@ https://beltoforion.de/en/wator/images/wator_medium.webm + +This solution aims to completely remove any systematic approach +to the Wa-Tor planet, and utilise fully random methods. + +The constants are a working set that allows the Wa-Tor planet +to result in one of the three possible results. +""" + +from collections.abc import Callable +from random import randint, shuffle +from time import sleep +from typing import Any, Literal + +WIDTH = 50 # Width of the Wa-Tor planet +HEIGHT = 50 # Height of the Wa-Tor planet + +PREY_INITIAL_COUNT = 30 # The initial number of prey entities +PREY_REPRODUCTION_TIME = 5 # The chronons before reproducing + +PREDATOR_INITIAL_COUNT = 50 # The initial number of predator entities +# The initial energy value of predator entities +PREDATOR_INITIAL_ENERGY_VALUE = 15 +# The energy value provided when consuming prey +PREDATOR_FOOD_VALUE = 5 +PREDATOR_REPRODUCTION_TIME = 20 # The chronons before reproducing + +MAX_ENTITIES = 500 # The max number of organisms on the board +# The number of entities to delete from the unbalanced side +DELETE_UNBALANCED_ENTITIES = 50 + + +class Entity: + """ + Represents an entity (either prey or predator). + + >>> e = Entity(True, coords=(0, 0)) + >>> e.prey + True + >>> e.coords + (0, 0) + >>> e.alive + True + """ + + def __init__(self, prey: bool, coords: tuple[int, int]): + self.prey = prey + # The (row, col) pos of the entity + self.coords = coords + + self.remaining_reproduction_time = ( + PREY_REPRODUCTION_TIME if prey is True else PREDATOR_REPRODUCTION_TIME + ) + self.energy_value = None if prey is True else PREDATOR_INITIAL_ENERGY_VALUE + self.alive = True + + def reset_reproduction_time(self) -> None: + """ + >>> e = Entity(True, coords=(0, 0)) + >>> e.reset_reproduction_time() + >>> e.remaining_reproduction_time == PREY_REPRODUCTION_TIME + True + >>> e = Entity(False, coords=(0, 0)) + >>> e.reset_reproduction_time() + >>> e.remaining_reproduction_time == PREDATOR_REPRODUCTION_TIME + True + """ + self.remaining_reproduction_time = ( + PREY_REPRODUCTION_TIME if self.prey is True else PREDATOR_REPRODUCTION_TIME + ) + + def __repr__(self) -> str: + """ + >>> Entity(prey=True, coords=(1, 1)) + + >>> Entity(prey=False, coords=(2, 1)) + + """ + repr_ = ( + f"entity_type={'prey' if self.prey is True else 'predator'}" + f" coords={self.coords}" + f" remaining_reproduction_time={self.remaining_reproduction_time}" + ) + if self.prey is False: + repr_ += f" energy={self.energy_value}" + return f"<{repr_}>" + + +class WaTor: + """ + Represents the main Wa-Tor algorithm. + + :attr time_passed: A function that is called every time + time passes (a chronon) in order to visually display + the new Wa-Tor planet. The time_passed function can block + using time.sleep to slow the algorithm progression. + + >>> wt = WaTor(10, 15) + >>> wt.width + 10 + >>> wt.height + 15 + >>> len(wt.planet) + 15 + >>> len(wt.planet[0]) + 10 + >>> len(wt.get_entities()) == PREDATOR_INITIAL_COUNT + PREY_INITIAL_COUNT + True + """ + + time_passed: Callable[["WaTor", int], None] | None + + def __init__(self, width: int, height: int) -> None: + self.width = width + self.height = height + self.time_passed = None + + self.planet: list[list[Entity | None]] = [ + [None for _ in range(width)] for _ in range(height) + ] + + # Populate planet with predators and prey randomly + for _ in range(PREY_INITIAL_COUNT): + self.add_entity(True) + for _ in range(PREDATOR_INITIAL_COUNT): + self.add_entity(False) + self.set_planet(self.planet) + + def set_planet(self, planet: list[list[Entity | None]]) -> None: + """ + Ease of access for testing + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> planet = [ + ... [None, None, None], + ... [None, Entity(True, coords=(1, 1)), None] + ... ] + >>> wt.set_planet(planet) + >>> wt.planet == planet + True + >>> wt.width + 3 + >>> wt.height + 2 + """ + self.planet = planet + self.width = len(planet[0]) + self.height = len(planet) + + def add_entity(self, prey: bool) -> None: + """ + Adds an entity, making sure the entity does + not override another entity + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> wt.set_planet([[None, None], [None, None]]) + >>> wt.add_entity(True) + >>> len(wt.get_entities()) + 1 + >>> wt.add_entity(False) + >>> len(wt.get_entities()) + 2 + """ + while True: + row, col = randint(0, self.height - 1), randint(0, self.width - 1) + if self.planet[row][col] is None: + break + self.planet[row][col] = Entity(prey=prey, coords=(row, col)) + + def get_entities(self) -> list[Entity]: + """ + Returns a list of all the entities within the planet. + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> len(wt.get_entities()) == PREDATOR_INITIAL_COUNT + PREY_INITIAL_COUNT + True + """ + start: Any = [] + return sum( + [[entity for entity in column if entity] for column in self.planet], + start=start, + ) + + def balance_predators_and_prey(self) -> None: + """ + Balances predators and preys so that prey + can not dominate the predators, blocking up + space for them to reproduce. + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> for i in range(2000): + ... row, col = i // HEIGHT, i % WIDTH + ... wt.planet[row][col] = Entity(True, coords=(row, col)) + >>> entities = len(wt.get_entities()) + >>> wt.balance_predators_and_prey() + >>> len(wt.get_entities()) == entities + False + """ + entities = self.get_entities() + shuffle(entities) + + if len(entities) >= MAX_ENTITIES - MAX_ENTITIES / 10: + prey = list(filter(lambda m: m.prey is True, entities)) + predators = list(filter(lambda m: m.prey is True, entities)) + + prey_count, predator_count = len(prey), len(predators) + + if prey_count > predator_count: + for entity in prey[:DELETE_UNBALANCED_ENTITIES]: + # Purge the first n entities of the prey + self.planet[entity.coords[0]][entity.coords[1]] = None + else: + for entity in predators[:DELETE_UNBALANCED_ENTITIES]: + # Purge the first n entities of the predators + self.planet[entity.coords[0]][entity.coords[1]] = None + + def get_surrounding_prey(self, entity: Entity) -> list[Entity]: + """ + Returns all the prey entities around (N, S, E, W) a predator entity. + + Subtly different to the try_to_move_to_unoccupied square. + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> wt.set_planet([ + ... [None, Entity(True, (0, 1)), None], + ... [None, Entity(False, (1, 1)), None], + ... [None, Entity(True, (2, 1)), None]]) + >>> wt.get_surrounding_prey( + ... Entity(False, (1, 1))) # doctest: +NORMALIZE_WHITESPACE + [, + ] + >>> wt.set_planet([[Entity(False, (0, 0))]]) + >>> wt.get_surrounding_prey(Entity(False, (0, 0))) + [] + >>> wt.set_planet([ + ... [Entity(True, (0, 0)), Entity(False, (1, 0)), Entity(False, (2, 0))], + ... [None, Entity(False, (1, 1)), Entity(True, (2, 1))], + ... [None, None, None]]) + >>> wt.get_surrounding_prey(Entity(False, (1, 0))) + [] + """ + coords = entity.coords + row, col = coords + surrounding_prey: list[Entity] = [] + + # Go through N, S, E, W with two booleans + # making four different combinations + for i in range(2): + for j in range(2): + vertical = bool(i) + positive = bool(j) + + # North (make sure in bounds) + if vertical is True and positive is True and row - 1 >= 0: + if ( + ent := self.planet[row - 1][col] + ) is not None and ent.prey is True: + surrounding_prey.append(ent) + # South (make sure in bounds) + elif vertical is True and positive is False and self.height > row + 1: + if ( + ent := self.planet[row + 1][col] + ) is not None and ent.prey is True: + surrounding_prey.append(ent) + # East (make sure in bounds) + elif vertical is False and positive is True and self.width > col + 1: + if ( + ent := self.planet[row][col + 1] + ) is not None and ent.prey is True: + surrounding_prey.append(ent) + # South (make sure in bounds) + elif vertical is False and positive is False and col - 1 >= 0: + if ( + ent := self.planet[row][col - 1] + ) is not None and ent.prey is True: + surrounding_prey.append(ent) + return surrounding_prey + + def move_and_reproduce( + self, entity: Entity, direction_orders: list[Literal["N", "E", "S", "W"]] + ) -> None: + """ + Attempts to move to an unoccupied neighbouring square + in either of the four directions (North, South, East, West). + If the move was successful and the remaining_reproduction time is + equal to 0, then a new prey or predator can also be created + in the previous square. + + :param direction_orders: Ordered list (like priority queue) depicting + order to attempt to move. Removes any systematic + approach of checking neighbouring squares. + + >>> planet = [ + ... [None, None, None], + ... [None, Entity(True, coords=(1, 1)), None], + ... [None, None, None] + ... ] + >>> wt = WaTor(WIDTH, HEIGHT) + >>> wt.set_planet(planet) + >>> wt.move_and_reproduce(Entity(True, coords=(1, 1)), direction_orders=["N"]) + >>> wt.planet # doctest: +NORMALIZE_WHITESPACE + [[None, , None], + [None, None, None], + [None, None, None]] + >>> wt.planet[0][0] = Entity(True, coords=(0, 0)) + >>> wt.planet[0][2] = None + >>> wt.move_and_reproduce(Entity(True, coords=(0, 1)), + ... direction_orders=["N", "W", "E", "S"]) + >>> wt.planet # doctest: +NORMALIZE_WHITESPACE + [[, None, + ], + [None, None, None], + [None, None, None]] + >>> wt.planet[0][1] = wt.planet[0][2] + >>> wt.planet[0][2] = None + >>> wt.move_and_reproduce(Entity(True, coords=(0, 1)), + ... direction_orders=["N", "W", "S", "E"]) + >>> wt.planet # doctest: +NORMALIZE_WHITESPACE + [[, None, None], + [None, , None], + [None, None, None]] + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> reproducable_entity = Entity(False, coords=(0, 1)) + >>> reproducable_entity.remaining_reproduction_time = 0 + >>> wt.planet = [[None, reproducable_entity]] + >>> wt.move_and_reproduce(reproducable_entity, + ... direction_orders=["N", "W", "S", "E"]) + >>> wt.planet # doctest: +NORMALIZE_WHITESPACE + [[, + ]] + """ + coords = entity.coords + row, col = coords + + for direction in direction_orders: + # If the direction is North and the northern square + # is within the top bound of the planet + if direction == "N" and row - 1 >= 0: + if self.planet[row - 1][col] is None: + self.planet[row - 1][col] = entity + entity.coords = (row - 1, col) + # If the direction is South and the southern square + # is within the bottom bound of the planet + elif direction == "S" and self.height > row + 1: + if self.planet[row + 1][col] is None: + self.planet[row + 1][col] = entity + entity.coords = (row + 1, col) + # If the direction is East and the eastern square + # is within the right bound of the planet + elif direction == "E" and self.width > col + 1: + if self.planet[row][col + 1] is None: + self.planet[row][col + 1] = entity + entity.coords = (row, col + 1) + # If the direction is West and the western square + # is within the left bound of the planet + elif direction == "W" and col - 1 >= 0: + if self.planet[row][col - 1] is None: + self.planet[row][col - 1] = entity + entity.coords = (row, col - 1) + + # See if move was successful (instead of adding a break) + # to each successful move + if coords != entity.coords: + # Remove the previous location of the entity + self.planet[row][col] = None + break + + # (2.) See if it possible to reproduce in previous square + if coords != entity.coords and entity.remaining_reproduction_time <= 0: + # Check if the entities on the planet is less than the max limit + if len(self.get_entities()) < MAX_ENTITIES: + # Reproduce in previous square + self.planet[row][col] = Entity(prey=entity.prey, coords=coords) + entity.reset_reproduction_time() + else: + entity.remaining_reproduction_time -= 1 + + def perform_prey_actions( + self, entity: Entity, direction_orders: list[Literal["N", "E", "S", "W"]] + ) -> None: + """ + Performs the actions for a prey entity + + For prey the rules are: + 1. At each chronon, a prey moves randomly to one of the adjacent unoccupied + squares. If there are no free squares, no movement takes place. + 2. Once a prey has survived a certain number of chronons it may reproduce. + This is done as it moves to a neighbouring square, + leaving behind a new prey in its old position. + Its reproduction time is also reset to zero. + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> reproducable_entity = Entity(True, coords=(0, 1)) + >>> reproducable_entity.remaining_reproduction_time = 0 + >>> wt.planet = [[None, reproducable_entity]] + >>> wt.perform_prey_actions(reproducable_entity, + ... direction_orders=["N", "W", "S", "E"]) + >>> wt.planet # doctest: +NORMALIZE_WHITESPACE + [[, + ]] + """ + self.move_and_reproduce(entity, direction_orders) + + def perform_predator_actions( + self, + entity: Entity, + occupied_by_prey_coords: tuple[int, int] | None, + direction_orders: list[Literal["N", "E", "S", "W"]], + ) -> None: + """ + Performs the actions for a predator entity + + :param occupied_by_prey_coords: Move to this location if there is prey there + + For predators the rules are: + 1. At each chronon, a predator moves randomly to an adjacent square occupied + by a prey. If there is none, the predator moves to a random adjacent + unoccupied square. If there are no free squares, no movement takes place. + 2. At each chronon, each predator is deprived of a unit of energy. + 3. Upon reaching zero energy, a predator dies. + 4. If a predator moves to a square occupied by a prey, + it eats the prey and earns a certain amount of energy. + 5. Once a predator has survived a certain number of chronons + it may reproduce in exactly the same way as the prey. + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> wt.set_planet([[Entity(True, coords=(0, 0)), Entity(False, coords=(0, 1))]]) + >>> wt.perform_predator_actions(Entity(False, coords=(0, 1)), (0, 0), []) + >>> wt.planet # doctest: +NORMALIZE_WHITESPACE + [[, None]] + """ + assert entity.energy_value is not None # [type checking] + + # (3.) If the entity has 0 energy, it will die + if entity.energy_value == 0: + self.planet[entity.coords[0]][entity.coords[1]] = None + return + + # (1.) Move to entity if possible + if occupied_by_prey_coords is not None: + # Kill the prey + prey = self.planet[occupied_by_prey_coords[0]][occupied_by_prey_coords[1]] + assert prey is not None + prey.alive = False + + # Move onto prey + self.planet[occupied_by_prey_coords[0]][occupied_by_prey_coords[1]] = entity + self.planet[entity.coords[0]][entity.coords[1]] = None + + entity.coords = occupied_by_prey_coords + # (4.) Eats the prey and earns energy + entity.energy_value += PREDATOR_FOOD_VALUE + else: + # (5.) If it has survived the certain number of chronons it will also + # reproduce in this function + self.move_and_reproduce(entity, direction_orders) + + # (2.) Each chronon, the predator is deprived of a unit of energy + entity.energy_value -= 1 + + def run(self, *, iteration_count: int) -> None: + """ + Emulate time passing by looping iteration_count times + + >>> wt = WaTor(WIDTH, HEIGHT) + >>> wt.run(iteration_count=PREDATOR_INITIAL_ENERGY_VALUE - 1) + >>> len(list(filter(lambda m: m.prey is False, + ... wt.get_entities()))) >= PREDATOR_INITIAL_COUNT + True + """ + for iter_num in range(iteration_count): + # Generate list of all entities in order to randomly + # pop an entity at a time to simulate true randomness + # This removes the systematic approach of iterating + # through each entity width by height + all_entities = self.get_entities() + + for __ in range(len(all_entities)): + entity = all_entities.pop(randint(0, len(all_entities) - 1)) + if entity.alive is False: + continue + + directions: list[Literal["N", "E", "S", "W"]] = ["N", "E", "S", "W"] + shuffle(directions) # Randomly shuffle directions + + if entity.prey: + self.perform_prey_actions(entity, directions) + else: + # Create list of surrounding prey + surrounding_prey = self.get_surrounding_prey(entity) + surrounding_prey_coords = None + + if surrounding_prey: + # Again, randomly shuffle directions + shuffle(surrounding_prey) + surrounding_prey_coords = surrounding_prey[0].coords + + self.perform_predator_actions( + entity, surrounding_prey_coords, directions + ) + + # Balance out the predators and prey + self.balance_predators_and_prey() + + if self.time_passed is not None: + # Call time_passed function for Wa-Tor planet + # visualisation in a terminal or a graph. + self.time_passed(self, iter_num) + + +def display_visually(wt: WaTor, iter_number: int, *, colour: bool = True) -> None: + """ + Visually displays the Wa-Tor planet using + an ascii code in terminal to clear and re-print + the Wa-Tor planet at intervals. + + Uses ascii colour codes to colourfully display + the predators and prey. + + (0x60f197) Prey = # + (0xfffff) Predator = x + + >>> wt = WaTor(30, 30) + >>> wt.set_planet([ + ... [Entity(True, coords=(0, 0)), Entity(False, coords=(0, 1)), None], + ... [Entity(False, coords=(1, 0)), None, Entity(False, coords=(1, 2))], + ... [None, Entity(True, coords=(2, 1)), None] + ... ]) + >>> display_visually(wt, 0, colour=False) # doctest: +NORMALIZE_WHITESPACE + # x . + x . x + . # . + + Iteration: 0 | Prey count: 2 | Predator count: 3 | + """ + if colour is True: + __import__("os").system("") + print("\x1b[0;0H\x1b[2J\x1b[?25l") + + reprint = "\x1b[0;0H" if colour is True else "" + ansii_colour_end = "\x1b[0m " if colour is True else " " + + planet = wt.planet + output = "" + + # Iterate over every entity in the planet + for i in range(len(planet)): + for j in range(len(planet[0])): + if (entity := planet[i][j]) is None: + output += " . " + else: + if colour is True: + output += ( + "\x1b[38;2;96;241;151m" + if entity.prey + else "\x1b[38;2;255;255;15m" + ) + output += f" {'#' if entity.prey else 'x'}{ansii_colour_end}" + + output += "\n" + + entities = wt.get_entities() + prey_count = len(list(filter(lambda m: m.prey is True, entities))) + + print( + f"{output}\n Iteration: {iter_number} | Prey count: {prey_count} | " + f"Predator count: {len(entities) - prey_count} | {reprint}" + ) + # Block the thread to be able to visualise seeing the algorithm + sleep(0.05) + + +if __name__ == "__main__": + # import doctest + + # doctest.testmod() + + wt = WaTor(WIDTH, HEIGHT) + wt.time_passed = display_visually + wt.run(iteration_count=100_000) From a0523653ac69917276b1c4434efac2da2dcfec10 Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Fri, 28 Jul 2023 16:02:00 +0000 Subject: [PATCH 02/21] updating DIRECTORY.md --- DIRECTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 77938f45011b..3f3a132846ab 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -74,6 +74,7 @@ * [Game Of Life](cellular_automata/game_of_life.py) * [Nagel Schrekenberg](cellular_automata/nagel_schrekenberg.py) * [One Dimensional](cellular_automata/one_dimensional.py) + * [Wa Tor](cellular_automata/wa_tor.py) ## Ciphers * [A1Z26](ciphers/a1z26.py) From f79680b915ab36b3f9e0b1149f47cb1d13594c47 Mon Sep 17 00:00:00 2001 From: CaedenPH Date: Fri, 28 Jul 2023 19:51:48 +0300 Subject: [PATCH 03/21] chore(quality): Implement algo-keeper bot changes --- cellular_automata/wa_tor.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index f9d19e363442..34eefc510e66 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -48,7 +48,7 @@ class Entity: True """ - def __init__(self, prey: bool, coords: tuple[int, int]): + def __init__(self, prey: bool, coords: tuple[int, int]) -> None: self.prey = prey # The (row, col) pos of the entity self.coords = coords @@ -205,8 +205,8 @@ def balance_predators_and_prey(self) -> None: shuffle(entities) if len(entities) >= MAX_ENTITIES - MAX_ENTITIES / 10: - prey = list(filter(lambda m: m.prey is True, entities)) - predators = list(filter(lambda m: m.prey is True, entities)) + prey = list(filter(lambda entity: entity.prey is True, entities)) + predators = list(filter(lambda entity: entity.prey is True, entities)) prey_count, predator_count = len(prey), len(predators) @@ -471,7 +471,7 @@ def run(self, *, iteration_count: int) -> None: >>> wt = WaTor(WIDTH, HEIGHT) >>> wt.run(iteration_count=PREDATOR_INITIAL_ENERGY_VALUE - 1) - >>> len(list(filter(lambda m: m.prey is False, + >>> len(list(filter(lambda entity: entity.prey is False, ... wt.get_entities()))) >= PREDATOR_INITIAL_COUNT True """ @@ -567,7 +567,7 @@ def display_visually(wt: WaTor, iter_number: int, *, colour: bool = True) -> Non output += "\n" entities = wt.get_entities() - prey_count = len(list(filter(lambda m: m.prey is True, entities))) + prey_count = len(list(filter(lambda entity: entity.prey is True, entities))) print( f"{output}\n Iteration: {iter_number} | Prey count: {prey_count} | " From 9289b51ddf68971cffb759825c0d8a87bca6e2b5 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Fri, 28 Jul 2023 21:17:11 +0300 Subject: [PATCH 04/21] Update cellular_automata/wa_tor.py Co-authored-by: Christian Clauss --- cellular_automata/wa_tor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index 34eefc510e66..c11e4e4ba1e3 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -121,7 +121,7 @@ def __init__(self, width: int, height: int) -> None: self.time_passed = None self.planet: list[list[Entity | None]] = [ - [None for _ in range(width)] for _ in range(height) + [None] * width for _ in range(height) ] # Populate planet with predators and prey randomly From 06fb41036564e4c5747b644e6a07d38e589eadb1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 18:17:37 +0000 Subject: [PATCH 05/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- cellular_automata/wa_tor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index c11e4e4ba1e3..7916d8c23fd5 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -120,9 +120,7 @@ def __init__(self, width: int, height: int) -> None: self.height = height self.time_passed = None - self.planet: list[list[Entity | None]] = [ - [None] * width for _ in range(height) - ] + self.planet: list[list[Entity | None]] = [[None] * width for _ in range(height)] # Populate planet with predators and prey randomly for _ in range(PREY_INITIAL_COUNT): From 4bb75740bea12006fea6e6b6552f9a87cdfae362 Mon Sep 17 00:00:00 2001 From: CaedenPH Date: Fri, 28 Jul 2023 21:48:12 +0300 Subject: [PATCH 06/21] refactor(repr): Return repr as python object --- cellular_automata/wa_tor.py | 52 +++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index 7916d8c23fd5..c7b46ccab8ad 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -77,18 +77,18 @@ def reset_reproduction_time(self) -> None: def __repr__(self) -> str: """ >>> Entity(prey=True, coords=(1, 1)) - - >>> Entity(prey=False, coords=(2, 1)) - + Entity(prey=True, coords=(1, 1), remaining_reproduction_time=5) + >>> Entity(prey=False, coords=(2, 1)) # doctest: +NORMALIZE_WHITESPACE + Entity(prey=False, coords=(2, 1), + remaining_reproduction_time=20, energy_value=15) """ repr_ = ( - f"entity_type={'prey' if self.prey is True else 'predator'}" - f" coords={self.coords}" - f" remaining_reproduction_time={self.remaining_reproduction_time}" + f"Entity(prey={self.prey}, coords={self.coords}, " + f"remaining_reproduction_time={self.remaining_reproduction_time}" ) - if self.prey is False: - repr_ += f" energy={self.energy_value}" - return f"<{repr_}>" + if self.energy_value is not None: + repr_ += f", energy_value={self.energy_value}" + return f"{repr_})" class WaTor: @@ -230,8 +230,8 @@ def get_surrounding_prey(self, entity: Entity) -> list[Entity]: ... [None, Entity(True, (2, 1)), None]]) >>> wt.get_surrounding_prey( ... Entity(False, (1, 1))) # doctest: +NORMALIZE_WHITESPACE - [, - ] + [Entity(prey=True, coords=(2, 1), remaining_reproduction_time=5), + Entity(prey=True, coords=(0, 1), remaining_reproduction_time=5)] >>> wt.set_planet([[Entity(False, (0, 0))]]) >>> wt.get_surrounding_prey(Entity(False, (0, 0))) [] @@ -240,7 +240,7 @@ def get_surrounding_prey(self, entity: Entity) -> list[Entity]: ... [None, Entity(False, (1, 1)), Entity(True, (2, 1))], ... [None, None, None]]) >>> wt.get_surrounding_prey(Entity(False, (1, 0))) - [] + [Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5)] """ coords = entity.coords row, col = coords @@ -302,7 +302,7 @@ def move_and_reproduce( >>> wt.set_planet(planet) >>> wt.move_and_reproduce(Entity(True, coords=(1, 1)), direction_orders=["N"]) >>> wt.planet # doctest: +NORMALIZE_WHITESPACE - [[None, , None], + [[None, Entity(prey=True, coords=(0, 1), remaining_reproduction_time=4), None], [None, None, None], [None, None, None]] >>> wt.planet[0][0] = Entity(True, coords=(0, 0)) @@ -310,8 +310,8 @@ def move_and_reproduce( >>> wt.move_and_reproduce(Entity(True, coords=(0, 1)), ... direction_orders=["N", "W", "E", "S"]) >>> wt.planet # doctest: +NORMALIZE_WHITESPACE - [[, None, - ], + [[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5), None, + Entity(prey=True, coords=(0, 2), remaining_reproduction_time=4)], [None, None, None], [None, None, None]] >>> wt.planet[0][1] = wt.planet[0][2] @@ -319,8 +319,8 @@ def move_and_reproduce( >>> wt.move_and_reproduce(Entity(True, coords=(0, 1)), ... direction_orders=["N", "W", "S", "E"]) >>> wt.planet # doctest: +NORMALIZE_WHITESPACE - [[, None, None], - [None, , None], + [[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5), None, None], + [None, Entity(prey=True, coords=(1, 1), remaining_reproduction_time=4), None], [None, None, None]] >>> wt = WaTor(WIDTH, HEIGHT) @@ -330,8 +330,10 @@ def move_and_reproduce( >>> wt.move_and_reproduce(reproducable_entity, ... direction_orders=["N", "W", "S", "E"]) >>> wt.planet # doctest: +NORMALIZE_WHITESPACE - [[, - ]] + [[Entity(prey=False, coords=(0, 0), + remaining_reproduction_time=20, energy_value=15), + Entity(prey=False, coords=(0, 1), remaining_reproduction_time=20, + energy_value=15)]] """ coords = entity.coords row, col = coords @@ -400,8 +402,8 @@ def perform_prey_actions( >>> wt.perform_prey_actions(reproducable_entity, ... direction_orders=["N", "W", "S", "E"]) >>> wt.planet # doctest: +NORMALIZE_WHITESPACE - [[, - ]] + [[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5), + Entity(prey=True, coords=(0, 1), remaining_reproduction_time=5)]] """ self.move_and_reproduce(entity, direction_orders) @@ -431,8 +433,8 @@ def perform_predator_actions( >>> wt.set_planet([[Entity(True, coords=(0, 0)), Entity(False, coords=(0, 1))]]) >>> wt.perform_predator_actions(Entity(False, coords=(0, 1)), (0, 0), []) >>> wt.planet # doctest: +NORMALIZE_WHITESPACE - [[, None]] + [[Entity(prey=False, coords=(0, 0), + remaining_reproduction_time=20, energy_value=19), None]] """ assert entity.energy_value is not None # [type checking] @@ -576,9 +578,9 @@ def display_visually(wt: WaTor, iter_number: int, *, colour: bool = True) -> Non if __name__ == "__main__": - # import doctest + import doctest - # doctest.testmod() + doctest.testmod() wt = WaTor(WIDTH, HEIGHT) wt.time_passed = display_visually From 9f23c25f965f8873c41e7e7bfe977867fd0d7fd3 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 30 Jul 2023 19:29:49 +0100 Subject: [PATCH 07/21] Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng --- cellular_automata/wa_tor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index c7b46ccab8ad..c32f7c02ee85 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -551,9 +551,9 @@ def display_visually(wt: WaTor, iter_number: int, *, colour: bool = True) -> Non output = "" # Iterate over every entity in the planet - for i in range(len(planet)): - for j in range(len(planet[0])): - if (entity := planet[i][j]) is None: + for row in planet: + for entity in row: + if entity is None: output += " . " else: if colour is True: From 8554b7194756cb13ccf6b01b0d66fc37a3162f62 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 30 Jul 2023 19:30:41 +0100 Subject: [PATCH 08/21] Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng --- cellular_automata/wa_tor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index c32f7c02ee85..33f02c1a8568 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -540,7 +540,7 @@ def display_visually(wt: WaTor, iter_number: int, *, colour: bool = True) -> Non Iteration: 0 | Prey count: 2 | Predator count: 3 | """ - if colour is True: + if colour: __import__("os").system("") print("\x1b[0;0H\x1b[2J\x1b[?25l") From a9fb2ed3fa167e9c633c0f6dd45aabf838605f17 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 30 Jul 2023 19:31:40 +0100 Subject: [PATCH 09/21] Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng --- cellular_automata/wa_tor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index 33f02c1a8568..cac151b6d815 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -544,8 +544,8 @@ def display_visually(wt: WaTor, iter_number: int, *, colour: bool = True) -> Non __import__("os").system("") print("\x1b[0;0H\x1b[2J\x1b[?25l") - reprint = "\x1b[0;0H" if colour is True else "" - ansii_colour_end = "\x1b[0m " if colour is True else " " + reprint = "\x1b[0;0H" if colour else "" + ansii_colour_end = "\x1b[0m " if colour else " " planet = wt.planet output = "" From e659bd100db988e27174aacca800fc4864f52424 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 30 Jul 2023 19:32:58 +0100 Subject: [PATCH 10/21] Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng --- cellular_automata/wa_tor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index cac151b6d815..4a961c8772d2 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -567,7 +567,7 @@ def display_visually(wt: WaTor, iter_number: int, *, colour: bool = True) -> Non output += "\n" entities = wt.get_entities() - prey_count = len(list(filter(lambda entity: entity.prey is True, entities))) + prey_count = sum(entity.prey for entity in entities) print( f"{output}\n Iteration: {iter_number} | Prey count: {prey_count} | " From d277dba404ddef3917fc804e6e0453fbf412b4e9 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 30 Jul 2023 19:34:00 +0100 Subject: [PATCH 11/21] Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng --- cellular_automata/wa_tor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index 4a961c8772d2..17bcd36073fc 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -54,7 +54,7 @@ def __init__(self, prey: bool, coords: tuple[int, int]) -> None: self.coords = coords self.remaining_reproduction_time = ( - PREY_REPRODUCTION_TIME if prey is True else PREDATOR_REPRODUCTION_TIME + PREY_REPRODUCTION_TIME if prey else PREDATOR_REPRODUCTION_TIME ) self.energy_value = None if prey is True else PREDATOR_INITIAL_ENERGY_VALUE self.alive = True From 9d8afeb9a1cb30d72d14bf9e5adff51d6813375b Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 30 Jul 2023 19:35:18 +0100 Subject: [PATCH 12/21] Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng --- cellular_automata/wa_tor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index 17bcd36073fc..726c9a56bfed 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -167,8 +167,8 @@ def add_entity(self, prey: bool) -> None: while True: row, col = randint(0, self.height - 1), randint(0, self.width - 1) if self.planet[row][col] is None: - break - self.planet[row][col] = Entity(prey=prey, coords=(row, col)) + self.planet[row][col] = Entity(prey=prey, coords=(row, col)) + return def get_entities(self) -> list[Entity]: """ From a87d6720d547a1d8f9e5ca31b026fc8a2b435688 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 30 Jul 2023 19:35:39 +0100 Subject: [PATCH 13/21] Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng --- cellular_automata/wa_tor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index 726c9a56bfed..f17580357982 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -124,9 +124,9 @@ def __init__(self, width: int, height: int) -> None: # Populate planet with predators and prey randomly for _ in range(PREY_INITIAL_COUNT): - self.add_entity(True) + self.add_entity(prey=True) for _ in range(PREDATOR_INITIAL_COUNT): - self.add_entity(False) + self.add_entity(prey=False) self.set_planet(self.planet) def set_planet(self, planet: list[list[Entity | None]]) -> None: From bf422b1f32500dd4c8504c0fae0bbad288c38c4d Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 30 Jul 2023 19:37:11 +0100 Subject: [PATCH 14/21] Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng --- cellular_automata/wa_tor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index f17580357982..c87561f1709c 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -203,7 +203,7 @@ def balance_predators_and_prey(self) -> None: shuffle(entities) if len(entities) >= MAX_ENTITIES - MAX_ENTITIES / 10: - prey = list(filter(lambda entity: entity.prey is True, entities)) + prey = [entity for entity in entities if entity.prey] predators = list(filter(lambda entity: entity.prey is True, entities)) prey_count, predator_count = len(prey), len(predators) From adcc4ff65962696ece40ee6069adb19e5a404d09 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 30 Jul 2023 19:37:40 +0100 Subject: [PATCH 15/21] Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng --- cellular_automata/wa_tor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index c87561f1709c..cb9252aed5ea 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -204,7 +204,7 @@ def balance_predators_and_prey(self) -> None: if len(entities) >= MAX_ENTITIES - MAX_ENTITIES / 10: prey = [entity for entity in entities if entity.prey] - predators = list(filter(lambda entity: entity.prey is True, entities)) + predators = [entity for entity in entities if not entity.prey] prey_count, predator_count = len(prey), len(predators) From 7dc67884d1ca499fc780ccad7aa05e9ebcd7d9e7 Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 30 Jul 2023 19:38:09 +0100 Subject: [PATCH 16/21] Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng --- cellular_automata/wa_tor.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index cb9252aed5ea..7e7860fe41ae 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -208,14 +208,13 @@ def balance_predators_and_prey(self) -> None: prey_count, predator_count = len(prey), len(predators) - if prey_count > predator_count: - for entity in prey[:DELETE_UNBALANCED_ENTITIES]: - # Purge the first n entities of the prey - self.planet[entity.coords[0]][entity.coords[1]] = None - else: - for entity in predators[:DELETE_UNBALANCED_ENTITIES]: - # Purge the first n entities of the predators - self.planet[entity.coords[0]][entity.coords[1]] = None + entities_to_purge = ( + prey[:DELETE_UNBALANCED_ENTITIES] + if prey_count > predator_count + else predators[:DELETE_UNBALANCED_ENTITIES] + ) + for entity in entities_to_purge: + self.planet[entity.coords[0]][entity.coords[1]] = None def get_surrounding_prey(self, entity: Entity) -> list[Entity]: """ From 86702995988cc26f927378ef9a74fbf8bb39975a Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sun, 30 Jul 2023 19:40:24 +0100 Subject: [PATCH 17/21] Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng --- cellular_automata/wa_tor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index 7e7860fe41ae..4dee3b4e59a6 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -334,8 +334,7 @@ def move_and_reproduce( Entity(prey=False, coords=(0, 1), remaining_reproduction_time=20, energy_value=15)]] """ - coords = entity.coords - row, col = coords + row, col = coords = entity.coords for direction in direction_orders: # If the direction is North and the northern square From 5ba7243e12d1e0f3a2145f140682885a59ad7de5 Mon Sep 17 00:00:00 2001 From: CaedenPH Date: Sun, 30 Jul 2023 20:04:16 +0100 Subject: [PATCH 18/21] refactor(display): Rename to display_visually to visualise --- cellular_automata/wa_tor.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index 4dee3b4e59a6..620ce04ff01f 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -513,7 +513,7 @@ def run(self, *, iteration_count: int) -> None: self.time_passed(self, iter_num) -def display_visually(wt: WaTor, iter_number: int, *, colour: bool = True) -> None: +def visualise(wt: WaTor, iter_number: int, *, colour: bool = True) -> None: """ Visually displays the Wa-Tor planet using an ascii code in terminal to clear and re-print @@ -531,7 +531,7 @@ def display_visually(wt: WaTor, iter_number: int, *, colour: bool = True) -> Non ... [Entity(False, coords=(1, 0)), None, Entity(False, coords=(1, 2))], ... [None, Entity(True, coords=(2, 1)), None] ... ]) - >>> display_visually(wt, 0, colour=False) # doctest: +NORMALIZE_WHITESPACE + >>> visualise(wt, 0, colour=False) # doctest: +NORMALIZE_WHITESPACE # x . x . x . # . @@ -543,7 +543,7 @@ def display_visually(wt: WaTor, iter_number: int, *, colour: bool = True) -> Non print("\x1b[0;0H\x1b[2J\x1b[?25l") reprint = "\x1b[0;0H" if colour else "" - ansii_colour_end = "\x1b[0m " if colour else " " + ansi_colour_end = "\x1b[0m " if colour else " " planet = wt.planet output = "" @@ -560,7 +560,7 @@ def display_visually(wt: WaTor, iter_number: int, *, colour: bool = True) -> Non if entity.prey else "\x1b[38;2;255;255;15m" ) - output += f" {'#' if entity.prey else 'x'}{ansii_colour_end}" + output += f" {'#' if entity.prey else 'x'}{ansi_colour_end}" output += "\n" @@ -581,5 +581,5 @@ def display_visually(wt: WaTor, iter_number: int, *, colour: bool = True) -> Non doctest.testmod() wt = WaTor(WIDTH, HEIGHT) - wt.time_passed = display_visually + wt.time_passed = visualise wt.run(iteration_count=100_000) From e6f3995219c4f82d03ba3b9c80ee5a4b65d1d622 Mon Sep 17 00:00:00 2001 From: CaedenPH Date: Sun, 30 Jul 2023 20:07:17 +0100 Subject: [PATCH 19/21] refactor(wa-tor): Use double for loop --- cellular_automata/wa_tor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index 620ce04ff01f..2a9161201d2d 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -15,7 +15,7 @@ from collections.abc import Callable from random import randint, shuffle from time import sleep -from typing import Any, Literal +from typing import Literal WIDTH = 50 # Width of the Wa-Tor planet HEIGHT = 50 # Height of the Wa-Tor planet @@ -178,11 +178,11 @@ def get_entities(self) -> list[Entity]: >>> len(wt.get_entities()) == PREDATOR_INITIAL_COUNT + PREY_INITIAL_COUNT True """ - start: Any = [] - return sum( - [[entity for entity in column if entity] for column in self.planet], - start=start, - ) + return [entity + for column in self.planet + for entity in column + if entity + ] def balance_predators_and_prey(self) -> None: """ From 318e0873972c6c0e4b7f77313f26712ba965faa9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 30 Jul 2023 19:07:44 +0000 Subject: [PATCH 20/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- cellular_automata/wa_tor.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index 2a9161201d2d..528ce35eb1ba 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -178,11 +178,7 @@ def get_entities(self) -> list[Entity]: >>> len(wt.get_entities()) == PREDATOR_INITIAL_COUNT + PREY_INITIAL_COUNT True """ - return [entity - for column in self.planet - for entity in column - if entity - ] + return [entity for column in self.planet for entity in column if entity] def balance_predators_and_prey(self) -> None: """ From 93f7b38ee01e3475a6c80c6a3abf509d46c81bde Mon Sep 17 00:00:00 2001 From: caedenph Date: Sun, 13 Aug 2023 23:38:50 +0100 Subject: [PATCH 21/21] chore(wa-tor): Implement suggestions from code review --- cellular_automata/wa_tor.py | 107 +++++++++++++----------------------- 1 file changed, 38 insertions(+), 69 deletions(-) diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py index 528ce35eb1ba..e423d1595bdb 100644 --- a/cellular_automata/wa_tor.py +++ b/cellular_automata/wa_tor.py @@ -225,8 +225,8 @@ def get_surrounding_prey(self, entity: Entity) -> list[Entity]: ... [None, Entity(True, (2, 1)), None]]) >>> wt.get_surrounding_prey( ... Entity(False, (1, 1))) # doctest: +NORMALIZE_WHITESPACE - [Entity(prey=True, coords=(2, 1), remaining_reproduction_time=5), - Entity(prey=True, coords=(0, 1), remaining_reproduction_time=5)] + [Entity(prey=True, coords=(0, 1), remaining_reproduction_time=5), + Entity(prey=True, coords=(2, 1), remaining_reproduction_time=5)] >>> wt.set_planet([[Entity(False, (0, 0))]]) >>> wt.get_surrounding_prey(Entity(False, (0, 0))) [] @@ -237,42 +237,22 @@ def get_surrounding_prey(self, entity: Entity) -> list[Entity]: >>> wt.get_surrounding_prey(Entity(False, (1, 0))) [Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5)] """ - coords = entity.coords - row, col = coords - surrounding_prey: list[Entity] = [] - - # Go through N, S, E, W with two booleans - # making four different combinations - for i in range(2): - for j in range(2): - vertical = bool(i) - positive = bool(j) - - # North (make sure in bounds) - if vertical is True and positive is True and row - 1 >= 0: - if ( - ent := self.planet[row - 1][col] - ) is not None and ent.prey is True: - surrounding_prey.append(ent) - # South (make sure in bounds) - elif vertical is True and positive is False and self.height > row + 1: - if ( - ent := self.planet[row + 1][col] - ) is not None and ent.prey is True: - surrounding_prey.append(ent) - # East (make sure in bounds) - elif vertical is False and positive is True and self.width > col + 1: - if ( - ent := self.planet[row][col + 1] - ) is not None and ent.prey is True: - surrounding_prey.append(ent) - # South (make sure in bounds) - elif vertical is False and positive is False and col - 1 >= 0: - if ( - ent := self.planet[row][col - 1] - ) is not None and ent.prey is True: - surrounding_prey.append(ent) - return surrounding_prey + row, col = entity.coords + adjacent: list[tuple[int, int]] = [ + (row - 1, col), # North + (row + 1, col), # South + (row, col - 1), # West + (row, col + 1), # East + ] + + return [ + ent + for r, c in adjacent + if 0 <= r < self.height + and 0 <= c < self.width + and (ent := self.planet[r][c]) is not None + and ent.prey + ] def move_and_reproduce( self, entity: Entity, direction_orders: list[Literal["N", "E", "S", "W"]] @@ -301,7 +281,6 @@ def move_and_reproduce( [None, None, None], [None, None, None]] >>> wt.planet[0][0] = Entity(True, coords=(0, 0)) - >>> wt.planet[0][2] = None >>> wt.move_and_reproduce(Entity(True, coords=(0, 1)), ... direction_orders=["N", "W", "E", "S"]) >>> wt.planet # doctest: +NORMALIZE_WHITESPACE @@ -332,37 +311,27 @@ def move_and_reproduce( """ row, col = coords = entity.coords - for direction in direction_orders: - # If the direction is North and the northern square - # is within the top bound of the planet - if direction == "N" and row - 1 >= 0: - if self.planet[row - 1][col] is None: - self.planet[row - 1][col] = entity - entity.coords = (row - 1, col) - # If the direction is South and the southern square - # is within the bottom bound of the planet - elif direction == "S" and self.height > row + 1: - if self.planet[row + 1][col] is None: - self.planet[row + 1][col] = entity - entity.coords = (row + 1, col) - # If the direction is East and the eastern square - # is within the right bound of the planet - elif direction == "E" and self.width > col + 1: - if self.planet[row][col + 1] is None: - self.planet[row][col + 1] = entity - entity.coords = (row, col + 1) - # If the direction is West and the western square - # is within the left bound of the planet - elif direction == "W" and col - 1 >= 0: - if self.planet[row][col - 1] is None: - self.planet[row][col - 1] = entity - entity.coords = (row, col - 1) - - # See if move was successful (instead of adding a break) - # to each successful move - if coords != entity.coords: - # Remove the previous location of the entity + adjacent_squares: dict[Literal["N", "E", "S", "W"], tuple[int, int]] = { + "N": (row - 1, col), # North + "S": (row + 1, col), # South + "W": (row, col - 1), # West + "E": (row, col + 1), # East + } + # Weight adjacent locations + adjacent: list[tuple[int, int]] = [] + for order in direction_orders: + adjacent.append(adjacent_squares[order]) + + for r, c in adjacent: + if ( + 0 <= r < self.height + and 0 <= c < self.width + and self.planet[r][c] is None + ): + # Move entity to empty adjacent square + self.planet[r][c] = entity self.planet[row][col] = None + entity.coords = (r, c) break # (2.) See if it possible to reproduce in previous square