Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9d86d4e

Browse files
CaedenPHgithub-actionscclausspre-commit-ci[bot]tianyizheng02
authoredAug 14, 2023
Create wa-tor algorithm (TheAlgorithms#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 <[email protected]> * [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 <[email protected]> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <[email protected]> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <[email protected]> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <[email protected]> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <[email protected]> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <[email protected]> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <[email protected]> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <[email protected]> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <[email protected]> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <[email protected]> * Update cellular_automata/wa_tor.py Co-authored-by: Tianyi Zheng <[email protected]> * 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 <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tianyi Zheng <[email protected]>
1 parent 4f2a346 commit 9d86d4e

File tree

2 files changed

+551
-0
lines changed

2 files changed

+551
-0
lines changed
 

‎DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
* [Game Of Life](cellular_automata/game_of_life.py)
7575
* [Nagel Schrekenberg](cellular_automata/nagel_schrekenberg.py)
7676
* [One Dimensional](cellular_automata/one_dimensional.py)
77+
* [Wa Tor](cellular_automata/wa_tor.py)
7778

7879
## Ciphers
7980
* [A1Z26](ciphers/a1z26.py)

‎cellular_automata/wa_tor.py

Lines changed: 550 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,550 @@
1+
"""
2+
Wa-Tor algorithm (1984)
3+
4+
@ https://en.wikipedia.org/wiki/Wa-Tor
5+
@ https://beltoforion.de/en/wator/
6+
@ https://beltoforion.de/en/wator/images/wator_medium.webm
7+
8+
This solution aims to completely remove any systematic approach
9+
to the Wa-Tor planet, and utilise fully random methods.
10+
11+
The constants are a working set that allows the Wa-Tor planet
12+
to result in one of the three possible results.
13+
"""
14+
15+
from collections.abc import Callable
16+
from random import randint, shuffle
17+
from time import sleep
18+
from typing import Literal
19+
20+
WIDTH = 50 # Width of the Wa-Tor planet
21+
HEIGHT = 50 # Height of the Wa-Tor planet
22+
23+
PREY_INITIAL_COUNT = 30 # The initial number of prey entities
24+
PREY_REPRODUCTION_TIME = 5 # The chronons before reproducing
25+
26+
PREDATOR_INITIAL_COUNT = 50 # The initial number of predator entities
27+
# The initial energy value of predator entities
28+
PREDATOR_INITIAL_ENERGY_VALUE = 15
29+
# The energy value provided when consuming prey
30+
PREDATOR_FOOD_VALUE = 5
31+
PREDATOR_REPRODUCTION_TIME = 20 # The chronons before reproducing
32+
33+
MAX_ENTITIES = 500 # The max number of organisms on the board
34+
# The number of entities to delete from the unbalanced side
35+
DELETE_UNBALANCED_ENTITIES = 50
36+
37+
38+
class Entity:
39+
"""
40+
Represents an entity (either prey or predator).
41+
42+
>>> e = Entity(True, coords=(0, 0))
43+
>>> e.prey
44+
True
45+
>>> e.coords
46+
(0, 0)
47+
>>> e.alive
48+
True
49+
"""
50+
51+
def __init__(self, prey: bool, coords: tuple[int, int]) -> None:
52+
self.prey = prey
53+
# The (row, col) pos of the entity
54+
self.coords = coords
55+
56+
self.remaining_reproduction_time = (
57+
PREY_REPRODUCTION_TIME if prey else PREDATOR_REPRODUCTION_TIME
58+
)
59+
self.energy_value = None if prey is True else PREDATOR_INITIAL_ENERGY_VALUE
60+
self.alive = True
61+
62+
def reset_reproduction_time(self) -> None:
63+
"""
64+
>>> e = Entity(True, coords=(0, 0))
65+
>>> e.reset_reproduction_time()
66+
>>> e.remaining_reproduction_time == PREY_REPRODUCTION_TIME
67+
True
68+
>>> e = Entity(False, coords=(0, 0))
69+
>>> e.reset_reproduction_time()
70+
>>> e.remaining_reproduction_time == PREDATOR_REPRODUCTION_TIME
71+
True
72+
"""
73+
self.remaining_reproduction_time = (
74+
PREY_REPRODUCTION_TIME if self.prey is True else PREDATOR_REPRODUCTION_TIME
75+
)
76+
77+
def __repr__(self) -> str:
78+
"""
79+
>>> Entity(prey=True, coords=(1, 1))
80+
Entity(prey=True, coords=(1, 1), remaining_reproduction_time=5)
81+
>>> Entity(prey=False, coords=(2, 1)) # doctest: +NORMALIZE_WHITESPACE
82+
Entity(prey=False, coords=(2, 1),
83+
remaining_reproduction_time=20, energy_value=15)
84+
"""
85+
repr_ = (
86+
f"Entity(prey={self.prey}, coords={self.coords}, "
87+
f"remaining_reproduction_time={self.remaining_reproduction_time}"
88+
)
89+
if self.energy_value is not None:
90+
repr_ += f", energy_value={self.energy_value}"
91+
return f"{repr_})"
92+
93+
94+
class WaTor:
95+
"""
96+
Represents the main Wa-Tor algorithm.
97+
98+
:attr time_passed: A function that is called every time
99+
time passes (a chronon) in order to visually display
100+
the new Wa-Tor planet. The time_passed function can block
101+
using time.sleep to slow the algorithm progression.
102+
103+
>>> wt = WaTor(10, 15)
104+
>>> wt.width
105+
10
106+
>>> wt.height
107+
15
108+
>>> len(wt.planet)
109+
15
110+
>>> len(wt.planet[0])
111+
10
112+
>>> len(wt.get_entities()) == PREDATOR_INITIAL_COUNT + PREY_INITIAL_COUNT
113+
True
114+
"""
115+
116+
time_passed: Callable[["WaTor", int], None] | None
117+
118+
def __init__(self, width: int, height: int) -> None:
119+
self.width = width
120+
self.height = height
121+
self.time_passed = None
122+
123+
self.planet: list[list[Entity | None]] = [[None] * width for _ in range(height)]
124+
125+
# Populate planet with predators and prey randomly
126+
for _ in range(PREY_INITIAL_COUNT):
127+
self.add_entity(prey=True)
128+
for _ in range(PREDATOR_INITIAL_COUNT):
129+
self.add_entity(prey=False)
130+
self.set_planet(self.planet)
131+
132+
def set_planet(self, planet: list[list[Entity | None]]) -> None:
133+
"""
134+
Ease of access for testing
135+
136+
>>> wt = WaTor(WIDTH, HEIGHT)
137+
>>> planet = [
138+
... [None, None, None],
139+
... [None, Entity(True, coords=(1, 1)), None]
140+
... ]
141+
>>> wt.set_planet(planet)
142+
>>> wt.planet == planet
143+
True
144+
>>> wt.width
145+
3
146+
>>> wt.height
147+
2
148+
"""
149+
self.planet = planet
150+
self.width = len(planet[0])
151+
self.height = len(planet)
152+
153+
def add_entity(self, prey: bool) -> None:
154+
"""
155+
Adds an entity, making sure the entity does
156+
not override another entity
157+
158+
>>> wt = WaTor(WIDTH, HEIGHT)
159+
>>> wt.set_planet([[None, None], [None, None]])
160+
>>> wt.add_entity(True)
161+
>>> len(wt.get_entities())
162+
1
163+
>>> wt.add_entity(False)
164+
>>> len(wt.get_entities())
165+
2
166+
"""
167+
while True:
168+
row, col = randint(0, self.height - 1), randint(0, self.width - 1)
169+
if self.planet[row][col] is None:
170+
self.planet[row][col] = Entity(prey=prey, coords=(row, col))
171+
return
172+
173+
def get_entities(self) -> list[Entity]:
174+
"""
175+
Returns a list of all the entities within the planet.
176+
177+
>>> wt = WaTor(WIDTH, HEIGHT)
178+
>>> len(wt.get_entities()) == PREDATOR_INITIAL_COUNT + PREY_INITIAL_COUNT
179+
True
180+
"""
181+
return [entity for column in self.planet for entity in column if entity]
182+
183+
def balance_predators_and_prey(self) -> None:
184+
"""
185+
Balances predators and preys so that prey
186+
can not dominate the predators, blocking up
187+
space for them to reproduce.
188+
189+
>>> wt = WaTor(WIDTH, HEIGHT)
190+
>>> for i in range(2000):
191+
... row, col = i // HEIGHT, i % WIDTH
192+
... wt.planet[row][col] = Entity(True, coords=(row, col))
193+
>>> entities = len(wt.get_entities())
194+
>>> wt.balance_predators_and_prey()
195+
>>> len(wt.get_entities()) == entities
196+
False
197+
"""
198+
entities = self.get_entities()
199+
shuffle(entities)
200+
201+
if len(entities) >= MAX_ENTITIES - MAX_ENTITIES / 10:
202+
prey = [entity for entity in entities if entity.prey]
203+
predators = [entity for entity in entities if not entity.prey]
204+
205+
prey_count, predator_count = len(prey), len(predators)
206+
207+
entities_to_purge = (
208+
prey[:DELETE_UNBALANCED_ENTITIES]
209+
if prey_count > predator_count
210+
else predators[:DELETE_UNBALANCED_ENTITIES]
211+
)
212+
for entity in entities_to_purge:
213+
self.planet[entity.coords[0]][entity.coords[1]] = None
214+
215+
def get_surrounding_prey(self, entity: Entity) -> list[Entity]:
216+
"""
217+
Returns all the prey entities around (N, S, E, W) a predator entity.
218+
219+
Subtly different to the try_to_move_to_unoccupied square.
220+
221+
>>> wt = WaTor(WIDTH, HEIGHT)
222+
>>> wt.set_planet([
223+
... [None, Entity(True, (0, 1)), None],
224+
... [None, Entity(False, (1, 1)), None],
225+
... [None, Entity(True, (2, 1)), None]])
226+
>>> wt.get_surrounding_prey(
227+
... Entity(False, (1, 1))) # doctest: +NORMALIZE_WHITESPACE
228+
[Entity(prey=True, coords=(0, 1), remaining_reproduction_time=5),
229+
Entity(prey=True, coords=(2, 1), remaining_reproduction_time=5)]
230+
>>> wt.set_planet([[Entity(False, (0, 0))]])
231+
>>> wt.get_surrounding_prey(Entity(False, (0, 0)))
232+
[]
233+
>>> wt.set_planet([
234+
... [Entity(True, (0, 0)), Entity(False, (1, 0)), Entity(False, (2, 0))],
235+
... [None, Entity(False, (1, 1)), Entity(True, (2, 1))],
236+
... [None, None, None]])
237+
>>> wt.get_surrounding_prey(Entity(False, (1, 0)))
238+
[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5)]
239+
"""
240+
row, col = entity.coords
241+
adjacent: list[tuple[int, int]] = [
242+
(row - 1, col), # North
243+
(row + 1, col), # South
244+
(row, col - 1), # West
245+
(row, col + 1), # East
246+
]
247+
248+
return [
249+
ent
250+
for r, c in adjacent
251+
if 0 <= r < self.height
252+
and 0 <= c < self.width
253+
and (ent := self.planet[r][c]) is not None
254+
and ent.prey
255+
]
256+
257+
def move_and_reproduce(
258+
self, entity: Entity, direction_orders: list[Literal["N", "E", "S", "W"]]
259+
) -> None:
260+
"""
261+
Attempts to move to an unoccupied neighbouring square
262+
in either of the four directions (North, South, East, West).
263+
If the move was successful and the remaining_reproduction time is
264+
equal to 0, then a new prey or predator can also be created
265+
in the previous square.
266+
267+
:param direction_orders: Ordered list (like priority queue) depicting
268+
order to attempt to move. Removes any systematic
269+
approach of checking neighbouring squares.
270+
271+
>>> planet = [
272+
... [None, None, None],
273+
... [None, Entity(True, coords=(1, 1)), None],
274+
... [None, None, None]
275+
... ]
276+
>>> wt = WaTor(WIDTH, HEIGHT)
277+
>>> wt.set_planet(planet)
278+
>>> wt.move_and_reproduce(Entity(True, coords=(1, 1)), direction_orders=["N"])
279+
>>> wt.planet # doctest: +NORMALIZE_WHITESPACE
280+
[[None, Entity(prey=True, coords=(0, 1), remaining_reproduction_time=4), None],
281+
[None, None, None],
282+
[None, None, None]]
283+
>>> wt.planet[0][0] = Entity(True, coords=(0, 0))
284+
>>> wt.move_and_reproduce(Entity(True, coords=(0, 1)),
285+
... direction_orders=["N", "W", "E", "S"])
286+
>>> wt.planet # doctest: +NORMALIZE_WHITESPACE
287+
[[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5), None,
288+
Entity(prey=True, coords=(0, 2), remaining_reproduction_time=4)],
289+
[None, None, None],
290+
[None, None, None]]
291+
>>> wt.planet[0][1] = wt.planet[0][2]
292+
>>> wt.planet[0][2] = None
293+
>>> wt.move_and_reproduce(Entity(True, coords=(0, 1)),
294+
... direction_orders=["N", "W", "S", "E"])
295+
>>> wt.planet # doctest: +NORMALIZE_WHITESPACE
296+
[[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5), None, None],
297+
[None, Entity(prey=True, coords=(1, 1), remaining_reproduction_time=4), None],
298+
[None, None, None]]
299+
300+
>>> wt = WaTor(WIDTH, HEIGHT)
301+
>>> reproducable_entity = Entity(False, coords=(0, 1))
302+
>>> reproducable_entity.remaining_reproduction_time = 0
303+
>>> wt.planet = [[None, reproducable_entity]]
304+
>>> wt.move_and_reproduce(reproducable_entity,
305+
... direction_orders=["N", "W", "S", "E"])
306+
>>> wt.planet # doctest: +NORMALIZE_WHITESPACE
307+
[[Entity(prey=False, coords=(0, 0),
308+
remaining_reproduction_time=20, energy_value=15),
309+
Entity(prey=False, coords=(0, 1), remaining_reproduction_time=20,
310+
energy_value=15)]]
311+
"""
312+
row, col = coords = entity.coords
313+
314+
adjacent_squares: dict[Literal["N", "E", "S", "W"], tuple[int, int]] = {
315+
"N": (row - 1, col), # North
316+
"S": (row + 1, col), # South
317+
"W": (row, col - 1), # West
318+
"E": (row, col + 1), # East
319+
}
320+
# Weight adjacent locations
321+
adjacent: list[tuple[int, int]] = []
322+
for order in direction_orders:
323+
adjacent.append(adjacent_squares[order])
324+
325+
for r, c in adjacent:
326+
if (
327+
0 <= r < self.height
328+
and 0 <= c < self.width
329+
and self.planet[r][c] is None
330+
):
331+
# Move entity to empty adjacent square
332+
self.planet[r][c] = entity
333+
self.planet[row][col] = None
334+
entity.coords = (r, c)
335+
break
336+
337+
# (2.) See if it possible to reproduce in previous square
338+
if coords != entity.coords and entity.remaining_reproduction_time <= 0:
339+
# Check if the entities on the planet is less than the max limit
340+
if len(self.get_entities()) < MAX_ENTITIES:
341+
# Reproduce in previous square
342+
self.planet[row][col] = Entity(prey=entity.prey, coords=coords)
343+
entity.reset_reproduction_time()
344+
else:
345+
entity.remaining_reproduction_time -= 1
346+
347+
def perform_prey_actions(
348+
self, entity: Entity, direction_orders: list[Literal["N", "E", "S", "W"]]
349+
) -> None:
350+
"""
351+
Performs the actions for a prey entity
352+
353+
For prey the rules are:
354+
1. At each chronon, a prey moves randomly to one of the adjacent unoccupied
355+
squares. If there are no free squares, no movement takes place.
356+
2. Once a prey has survived a certain number of chronons it may reproduce.
357+
This is done as it moves to a neighbouring square,
358+
leaving behind a new prey in its old position.
359+
Its reproduction time is also reset to zero.
360+
361+
>>> wt = WaTor(WIDTH, HEIGHT)
362+
>>> reproducable_entity = Entity(True, coords=(0, 1))
363+
>>> reproducable_entity.remaining_reproduction_time = 0
364+
>>> wt.planet = [[None, reproducable_entity]]
365+
>>> wt.perform_prey_actions(reproducable_entity,
366+
... direction_orders=["N", "W", "S", "E"])
367+
>>> wt.planet # doctest: +NORMALIZE_WHITESPACE
368+
[[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5),
369+
Entity(prey=True, coords=(0, 1), remaining_reproduction_time=5)]]
370+
"""
371+
self.move_and_reproduce(entity, direction_orders)
372+
373+
def perform_predator_actions(
374+
self,
375+
entity: Entity,
376+
occupied_by_prey_coords: tuple[int, int] | None,
377+
direction_orders: list[Literal["N", "E", "S", "W"]],
378+
) -> None:
379+
"""
380+
Performs the actions for a predator entity
381+
382+
:param occupied_by_prey_coords: Move to this location if there is prey there
383+
384+
For predators the rules are:
385+
1. At each chronon, a predator moves randomly to an adjacent square occupied
386+
by a prey. If there is none, the predator moves to a random adjacent
387+
unoccupied square. If there are no free squares, no movement takes place.
388+
2. At each chronon, each predator is deprived of a unit of energy.
389+
3. Upon reaching zero energy, a predator dies.
390+
4. If a predator moves to a square occupied by a prey,
391+
it eats the prey and earns a certain amount of energy.
392+
5. Once a predator has survived a certain number of chronons
393+
it may reproduce in exactly the same way as the prey.
394+
395+
>>> wt = WaTor(WIDTH, HEIGHT)
396+
>>> wt.set_planet([[Entity(True, coords=(0, 0)), Entity(False, coords=(0, 1))]])
397+
>>> wt.perform_predator_actions(Entity(False, coords=(0, 1)), (0, 0), [])
398+
>>> wt.planet # doctest: +NORMALIZE_WHITESPACE
399+
[[Entity(prey=False, coords=(0, 0),
400+
remaining_reproduction_time=20, energy_value=19), None]]
401+
"""
402+
assert entity.energy_value is not None # [type checking]
403+
404+
# (3.) If the entity has 0 energy, it will die
405+
if entity.energy_value == 0:
406+
self.planet[entity.coords[0]][entity.coords[1]] = None
407+
return
408+
409+
# (1.) Move to entity if possible
410+
if occupied_by_prey_coords is not None:
411+
# Kill the prey
412+
prey = self.planet[occupied_by_prey_coords[0]][occupied_by_prey_coords[1]]
413+
assert prey is not None
414+
prey.alive = False
415+
416+
# Move onto prey
417+
self.planet[occupied_by_prey_coords[0]][occupied_by_prey_coords[1]] = entity
418+
self.planet[entity.coords[0]][entity.coords[1]] = None
419+
420+
entity.coords = occupied_by_prey_coords
421+
# (4.) Eats the prey and earns energy
422+
entity.energy_value += PREDATOR_FOOD_VALUE
423+
else:
424+
# (5.) If it has survived the certain number of chronons it will also
425+
# reproduce in this function
426+
self.move_and_reproduce(entity, direction_orders)
427+
428+
# (2.) Each chronon, the predator is deprived of a unit of energy
429+
entity.energy_value -= 1
430+
431+
def run(self, *, iteration_count: int) -> None:
432+
"""
433+
Emulate time passing by looping iteration_count times
434+
435+
>>> wt = WaTor(WIDTH, HEIGHT)
436+
>>> wt.run(iteration_count=PREDATOR_INITIAL_ENERGY_VALUE - 1)
437+
>>> len(list(filter(lambda entity: entity.prey is False,
438+
... wt.get_entities()))) >= PREDATOR_INITIAL_COUNT
439+
True
440+
"""
441+
for iter_num in range(iteration_count):
442+
# Generate list of all entities in order to randomly
443+
# pop an entity at a time to simulate true randomness
444+
# This removes the systematic approach of iterating
445+
# through each entity width by height
446+
all_entities = self.get_entities()
447+
448+
for __ in range(len(all_entities)):
449+
entity = all_entities.pop(randint(0, len(all_entities) - 1))
450+
if entity.alive is False:
451+
continue
452+
453+
directions: list[Literal["N", "E", "S", "W"]] = ["N", "E", "S", "W"]
454+
shuffle(directions) # Randomly shuffle directions
455+
456+
if entity.prey:
457+
self.perform_prey_actions(entity, directions)
458+
else:
459+
# Create list of surrounding prey
460+
surrounding_prey = self.get_surrounding_prey(entity)
461+
surrounding_prey_coords = None
462+
463+
if surrounding_prey:
464+
# Again, randomly shuffle directions
465+
shuffle(surrounding_prey)
466+
surrounding_prey_coords = surrounding_prey[0].coords
467+
468+
self.perform_predator_actions(
469+
entity, surrounding_prey_coords, directions
470+
)
471+
472+
# Balance out the predators and prey
473+
self.balance_predators_and_prey()
474+
475+
if self.time_passed is not None:
476+
# Call time_passed function for Wa-Tor planet
477+
# visualisation in a terminal or a graph.
478+
self.time_passed(self, iter_num)
479+
480+
481+
def visualise(wt: WaTor, iter_number: int, *, colour: bool = True) -> None:
482+
"""
483+
Visually displays the Wa-Tor planet using
484+
an ascii code in terminal to clear and re-print
485+
the Wa-Tor planet at intervals.
486+
487+
Uses ascii colour codes to colourfully display
488+
the predators and prey.
489+
490+
(0x60f197) Prey = #
491+
(0xfffff) Predator = x
492+
493+
>>> wt = WaTor(30, 30)
494+
>>> wt.set_planet([
495+
... [Entity(True, coords=(0, 0)), Entity(False, coords=(0, 1)), None],
496+
... [Entity(False, coords=(1, 0)), None, Entity(False, coords=(1, 2))],
497+
... [None, Entity(True, coords=(2, 1)), None]
498+
... ])
499+
>>> visualise(wt, 0, colour=False) # doctest: +NORMALIZE_WHITESPACE
500+
# x .
501+
x . x
502+
. # .
503+
<BLANKLINE>
504+
Iteration: 0 | Prey count: 2 | Predator count: 3 |
505+
"""
506+
if colour:
507+
__import__("os").system("")
508+
print("\x1b[0;0H\x1b[2J\x1b[?25l")
509+
510+
reprint = "\x1b[0;0H" if colour else ""
511+
ansi_colour_end = "\x1b[0m " if colour else " "
512+
513+
planet = wt.planet
514+
output = ""
515+
516+
# Iterate over every entity in the planet
517+
for row in planet:
518+
for entity in row:
519+
if entity is None:
520+
output += " . "
521+
else:
522+
if colour is True:
523+
output += (
524+
"\x1b[38;2;96;241;151m"
525+
if entity.prey
526+
else "\x1b[38;2;255;255;15m"
527+
)
528+
output += f" {'#' if entity.prey else 'x'}{ansi_colour_end}"
529+
530+
output += "\n"
531+
532+
entities = wt.get_entities()
533+
prey_count = sum(entity.prey for entity in entities)
534+
535+
print(
536+
f"{output}\n Iteration: {iter_number} | Prey count: {prey_count} | "
537+
f"Predator count: {len(entities) - prey_count} | {reprint}"
538+
)
539+
# Block the thread to be able to visualise seeing the algorithm
540+
sleep(0.05)
541+
542+
543+
if __name__ == "__main__":
544+
import doctest
545+
546+
doctest.testmod()
547+
548+
wt = WaTor(WIDTH, HEIGHT)
549+
wt.time_passed = visualise
550+
wt.run(iteration_count=100_000)

0 commit comments

Comments
 (0)
Please sign in to comment.