Adafruit_Learning_System_Gu.../Custom_LED_Animations/conways/conways.py
2025-01-23 15:10:43 -06:00

192 lines
6 KiB
Python

# SPDX-FileCopyrightText: 2024 Tim Cocks
#
# SPDX-License-Identifier: MIT
"""
ConwaysLifeAnimation helper class
"""
from micropython import const
from adafruit_led_animation.animation import Animation
from adafruit_led_animation.grid import PixelGrid, HORIZONTAL
def _is_pixel_off(pixel):
return pixel[0] == 0 and pixel[1] == 0 and pixel[2] == 0
class ConwaysLifeAnimation(Animation):
# Constants
DIRECTION_OFFSETS = [
(0, 1),
(0, -1),
(1, 0),
(-1, 0),
(1, 1),
(-1, 1),
(1, -1),
(-1, -1),
]
LIVE = const(0x01)
DEAD = const(0x00)
def __init__(
self,
pixel_object,
speed,
color,
width,
height,
initial_cells,
equilibrium_restart=True,
):
"""
Conway's Game of Life implementation. Watch the cells
live and die based on the classic rules.
:param pixel_object: The initialised LED object.
:param float speed: Animation refresh rate in seconds, e.g. ``0.1``.
:param color: the color to use for live cells
:param width: the width of the grid
:param height: the height of the grid
:param initial_cells: list of initial cells to be live
:param equilibrium_restart: whether to restart when the simulation gets stuck unchanging
"""
super().__init__(pixel_object, speed, color)
# list to hold which cells are live
self.drawn_pixels = []
# store the initial cells
self.initial_cells = initial_cells
# PixelGrid helper to access the strand as a 2D grid
self.pixel_grid = PixelGrid(
pixel_object, width, height, orientation=HORIZONTAL, alternating=False
)
# size of the grid
self.width = width
self.height = height
# equilibrium restart boolean
self.equilibrium_restart = equilibrium_restart
# counter to store how many turns since the last change
self.equilibrium_turns = 0
# self._init_cells()
def _is_grid_empty(self):
"""
Checks if the grid is empty.
:return: True if there are no live cells, False otherwise
"""
for y in range(self.height):
for x in range(self.width):
if not _is_pixel_off(self.pixel_grid[x][y]):
return False
return True
def _init_cells(self):
"""
Turn off all LEDs then turn on ones cooresponding to the initial_cells
:return: None
"""
self.pixel_grid.fill(0x000000)
for cell in self.initial_cells:
self.pixel_grid[cell] = self.color
def _count_neighbors(self, cell):
"""
Check how many live cell neighbors are found at the given location
:param cell: the location to check
:return: the number of live cell neighbors
"""
neighbors = 0
for direction in ConwaysLifeAnimation.DIRECTION_OFFSETS:
try:
if not _is_pixel_off(
self.pixel_grid[cell[0] + direction[0]][cell[1] + direction[1]]
):
neighbors += 1
except IndexError:
pass
return neighbors
def draw(self):
# pylint: disable=too-many-branches
"""
draw the current frame of the animation
:return: None
"""
# if there are no live cells
if self._is_grid_empty():
# spawn the inital_cells and return
self._init_cells()
return
# list to hold locations to despawn live cells
despawning_cells = []
# list to hold locations spawn new live cells
spawning_cells = []
# loop over the grid
for y in range(self.height):
for x in range(self.width):
# check and set the current cell type, live or dead
if _is_pixel_off(self.pixel_grid[x][y]):
cur_cell_type = ConwaysLifeAnimation.DEAD
else:
cur_cell_type = ConwaysLifeAnimation.LIVE
# get a count of the neigbors
neighbors = self._count_neighbors((x, y))
# if the current cell is alive
if cur_cell_type == ConwaysLifeAnimation.LIVE:
# if it has fewer than 2 neighbors
if neighbors < 2:
# add its location to the despawn list
despawning_cells.append((x, y))
# if it has more than 3 neighbors
if neighbors > 3:
# add its location to the despawn list
despawning_cells.append((x, y))
# if the current location is not a living cell
elif cur_cell_type == ConwaysLifeAnimation.DEAD:
# if it has exactly 3 neighbors
if neighbors == 3:
# add the current location to the spawn list
spawning_cells.append((x, y))
# loop over the despawn locations
for cell in despawning_cells:
# turn off LEDs at each location
self.pixel_grid[cell] = 0x000000
# loop over the spawn list
for cell in spawning_cells:
# turn on LEDs at each location
self.pixel_grid[cell] = self.color
# if equilibrium restart mode is enabled
if self.equilibrium_restart:
# if there were no cells spawned or despaned this round
if len(despawning_cells) == 0 and len(spawning_cells) == 0:
# increment equilibrium turns counter
self.equilibrium_turns += 1
# if the counter is 3 or higher
if self.equilibrium_turns >= 3:
# go back to the initial_cells
self._init_cells()
# reset the turns counter to zero
self.equilibrium_turns = 0