mirror of
https://github.com/TheAlgorithms/Python.git
synced 2024-11-23 21:11:08 +00:00
Create wa-tor algorithm (#8899)
* feat(cellular_automata): Create wa-tor algorithm * updating DIRECTORY.md * chore(quality): Implement algo-keeper bot changes * Update cellular_automata/wa_tor.py Co-authored-by: Christian Clauss <cclauss@me.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor(repr): Return repr as python object * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com> * refactor(display): Rename to display_visually to visualise * refactor(wa-tor): Use double for loop * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore(wa-tor): Implement suggestions from code review --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss <cclauss@me.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
This commit is contained in:
parent
4f2a346c27
commit
9d86d4edaa
|
@ -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)
|
||||
|
|
550
cellular_automata/wa_tor.py
Normal file
550
cellular_automata/wa_tor.py
Normal file
|
@ -0,0 +1,550 @@
|
|||
"""
|
||||
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 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]) -> None:
|
||||
self.prey = prey
|
||||
# The (row, col) pos of the entity
|
||||
self.coords = coords
|
||||
|
||||
self.remaining_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
|
||||
|
||||
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=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(prey={self.prey}, coords={self.coords}, "
|
||||
f"remaining_reproduction_time={self.remaining_reproduction_time}"
|
||||
)
|
||||
if self.energy_value is not None:
|
||||
repr_ += f", energy_value={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] * width for _ in range(height)]
|
||||
|
||||
# Populate planet with predators and prey randomly
|
||||
for _ in range(PREY_INITIAL_COUNT):
|
||||
self.add_entity(prey=True)
|
||||
for _ in range(PREDATOR_INITIAL_COUNT):
|
||||
self.add_entity(prey=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:
|
||||
self.planet[row][col] = Entity(prey=prey, coords=(row, col))
|
||||
return
|
||||
|
||||
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
|
||||
"""
|
||||
return [entity for column in self.planet for entity in column if entity]
|
||||
|
||||
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 = [entity for entity in entities if entity.prey]
|
||||
predators = [entity for entity in entities if not entity.prey]
|
||||
|
||||
prey_count, predator_count = len(prey), len(predators)
|
||||
|
||||
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]:
|
||||
"""
|
||||
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
|
||||
[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)]
|
||||
"""
|
||||
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"]]
|
||||
) -> 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)]]
|
||||
"""
|
||||
row, col = coords = entity.coords
|
||||
|
||||
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
|
||||
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
|
||||
[[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)
|
||||
|
||||
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
|
||||
[[Entity(prey=False, coords=(0, 0),
|
||||
remaining_reproduction_time=20, energy_value=19), 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 entity: entity.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 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
|
||||
. # .
|
||||
<BLANKLINE>
|
||||
Iteration: 0 | Prey count: 2 | Predator count: 3 |
|
||||
"""
|
||||
if colour:
|
||||
__import__("os").system("")
|
||||
print("\x1b[0;0H\x1b[2J\x1b[?25l")
|
||||
|
||||
reprint = "\x1b[0;0H" if colour else ""
|
||||
ansi_colour_end = "\x1b[0m " if colour else " "
|
||||
|
||||
planet = wt.planet
|
||||
output = ""
|
||||
|
||||
# Iterate over every entity in the planet
|
||||
for row in planet:
|
||||
for entity in row:
|
||||
if entity 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'}{ansi_colour_end}"
|
||||
|
||||
output += "\n"
|
||||
|
||||
entities = wt.get_entities()
|
||||
prey_count = sum(entity.prey for entity in 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 = visualise
|
||||
wt.run(iteration_count=100_000)
|
Loading…
Reference in New Issue
Block a user