cellular_automata.wa_tor

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

DELETE_UNBALANCED_ENTITIES

HEIGHT

MAX_ENTITIES

PREDATOR_FOOD_VALUE

PREDATOR_INITIAL_COUNT

PREDATOR_INITIAL_ENERGY_VALUE

PREDATOR_REPRODUCTION_TIME

PREY_INITIAL_COUNT

PREY_REPRODUCTION_TIME

WIDTH

wt

Classes

Entity

Represents an entity (either prey or predator).

WaTor

Represents the main Wa-Tor algorithm.

Functions

visualise(→ None)

Visually displays the Wa-Tor planet using

Module Contents

class cellular_automata.wa_tor.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
__repr__() str
>>> Entity(prey=True, coords=(1, 1))
Entity(prey=True, coords=(1, 1), remaining_reproduction_time=5)
>>> Entity(prey=False, coords=(2, 1))  
Entity(prey=False, coords=(2, 1),
remaining_reproduction_time=20, energy_value=15)
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
alive = True
coords
energy_value
prey
remaining_reproduction_time
class cellular_automata.wa_tor.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
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
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
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
get_surrounding_prey(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)))  
[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)]
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.

Parameters:

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  
[[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  
[[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  
[[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  
[[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)]]
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

Parameters:

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.

  1. At each chronon, each predator is deprived of a unit of energy.

  2. Upon reaching zero energy, a predator dies.

  3. 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  
[[Entity(prey=False, coords=(0, 0),
remaining_reproduction_time=20, energy_value=19), None]]
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.

  1. 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  
[[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5),
Entity(prey=True, coords=(0, 1), remaining_reproduction_time=5)]]
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
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
height
planet: list[list[Entity | None]]
time_passed: collections.abc.Callable[[WaTor, int], None] | None
width
cellular_automata.wa_tor.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)  
#  x  .
x  .  x
.  #  .

Iteration: 0 | Prey count: 2 | Predator count: 3 |
cellular_automata.wa_tor.DELETE_UNBALANCED_ENTITIES = 50
cellular_automata.wa_tor.HEIGHT = 50
cellular_automata.wa_tor.MAX_ENTITIES = 500
cellular_automata.wa_tor.PREDATOR_FOOD_VALUE = 5
cellular_automata.wa_tor.PREDATOR_INITIAL_COUNT = 50
cellular_automata.wa_tor.PREDATOR_INITIAL_ENERGY_VALUE = 15
cellular_automata.wa_tor.PREDATOR_REPRODUCTION_TIME = 20
cellular_automata.wa_tor.PREY_INITIAL_COUNT = 30
cellular_automata.wa_tor.PREY_REPRODUCTION_TIME = 5
cellular_automata.wa_tor.WIDTH = 50
cellular_automata.wa_tor.wt