cellular_automata.wa_tor ======================== .. py:module:: cellular_automata.wa_tor .. autoapi-nested-parse:: 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. Attributes ---------- .. autoapisummary:: cellular_automata.wa_tor.DELETE_UNBALANCED_ENTITIES cellular_automata.wa_tor.HEIGHT cellular_automata.wa_tor.MAX_ENTITIES cellular_automata.wa_tor.PREDATOR_FOOD_VALUE cellular_automata.wa_tor.PREDATOR_INITIAL_COUNT cellular_automata.wa_tor.PREDATOR_INITIAL_ENERGY_VALUE cellular_automata.wa_tor.PREDATOR_REPRODUCTION_TIME cellular_automata.wa_tor.PREY_INITIAL_COUNT cellular_automata.wa_tor.PREY_REPRODUCTION_TIME cellular_automata.wa_tor.WIDTH cellular_automata.wa_tor.wt Classes ------- .. autoapisummary:: cellular_automata.wa_tor.Entity cellular_automata.wa_tor.WaTor Functions --------- .. autoapisummary:: cellular_automata.wa_tor.visualise Module Contents --------------- .. py:class:: Entity(prey: bool, coords: tuple[int, int]) Represents an entity (either prey or predator). >>> e = Entity(True, coords=(0, 0)) >>> e.prey True >>> e.coords (0, 0) >>> e.alive True .. py:method:: __repr__() -> str >>> Entity(prey=True, coords=(1, 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) .. py:method:: reset_reproduction_time() -> 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 .. py:attribute:: alive :value: True .. py:attribute:: coords .. py:attribute:: energy_value :value: None .. py:attribute:: prey .. py:attribute:: remaining_reproduction_time :value: 5 .. py:class:: WaTor(width: int, height: int) 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 .. py:method:: add_entity(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 .. py:method:: balance_predators_and_prey() -> 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 .. py:method:: get_entities() -> 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 .. py:method:: get_surrounding_prey(entity: Entity) -> list[Entity] Returns all the prey entities around (N, S, E, W) a predator entity. Subtly different to the `move_and_reproduce`. >>> 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 [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))) [] >>> 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))) [Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5)] .. py:method:: move_and_reproduce(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, 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)) >>> wt.move_and_reproduce(Entity(True, coords=(0, 1)), ... direction_orders=["N", "W", "E", "S"]) >>> wt.planet # doctest: +NORMALIZE_WHITESPACE [[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] >>> 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 [[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) >>> 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 [[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)]] .. py:method:: perform_predator_actions(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 [[Entity(prey=False, coords=(0, 0), remaining_reproduction_time=20, energy_value=19), None]] .. py:method:: perform_prey_actions(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 [[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5), Entity(prey=True, coords=(0, 1), remaining_reproduction_time=5)]] .. py:method:: run(*, 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 entity: entity.prey is False, ... wt.get_entities()))) >= PREDATOR_INITIAL_COUNT True .. py:method:: set_planet(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 .. py:attribute:: height .. py:attribute:: planet :type: list[list[Entity | None]] .. py:attribute:: time_passed :type: collections.abc.Callable[[WaTor, int], None] | None .. py:attribute:: width .. py:function:: 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 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] ... ]) >>> visualise(wt, 0, colour=False) # doctest: +NORMALIZE_WHITESPACE # x . x . x . # . Iteration: 0 | Prey count: 2 | Predator count: 3 | .. py:data:: DELETE_UNBALANCED_ENTITIES :value: 50 .. py:data:: HEIGHT :value: 50 .. py:data:: MAX_ENTITIES :value: 500 .. py:data:: PREDATOR_FOOD_VALUE :value: 5 .. py:data:: PREDATOR_INITIAL_COUNT :value: 50 .. py:data:: PREDATOR_INITIAL_ENERGY_VALUE :value: 15 .. py:data:: PREDATOR_REPRODUCTION_TIME :value: 20 .. py:data:: PREY_INITIAL_COUNT :value: 30 .. py:data:: PREY_REPRODUCTION_TIME :value: 5 .. py:data:: WIDTH :value: 50 .. py:data:: wt