Skip to content

Commit 36785e3

Browse files
committed
strand sequence, and grid both versions
1 parent cf348e0 commit 36785e3

File tree

7 files changed

+736
-0
lines changed

7 files changed

+736
-0
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
"""
5+
Uses NeoPixel Featherwing connected to D10 and
6+
Dotstar Featherwing connected to D13, and D11.
7+
Update pins as needed for your connections.
8+
"""
9+
import board
10+
import neopixel
11+
import adafruit_dotstar as dotstar
12+
from conways import ConwaysLifeAnimation
13+
from snake import SnakeAnimation
14+
15+
# Update to match the pin connected to your NeoPixels
16+
pixel_pin = board.D10
17+
# Update to match the number of NeoPixels you have connected
18+
pixel_num = 32
19+
20+
# initialize the neopixels featherwing
21+
pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.02, auto_write=False)
22+
23+
# initialize the dotstar featherwing
24+
dots = dotstar.DotStar(board.D13, board.D11, 72, brightness=0.02)
25+
26+
# initial live cells for conways
27+
initial_cells = [
28+
(2, 1),
29+
(3, 1),
30+
(4, 1),
31+
(5, 1),
32+
(6, 1),
33+
]
34+
35+
# initialize the animations
36+
conways = ConwaysLifeAnimation(dots, 0.1, 0xff00ff, 12, 6, initial_cells)
37+
38+
snake = SnakeAnimation(pixels, speed=0.1, color=0xff00ff, width=8, height=4)
39+
40+
while True:
41+
# call animate to show the next animation frames
42+
conways.animate()
43+
snake.animate()
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# SPDX-FileCopyrightText: 2024 Tim Cocks
2+
#
3+
# SPDX-License-Identifier: MIT
4+
"""
5+
ConwaysLifeAnimation helper class
6+
"""
7+
from micropython import const
8+
9+
from adafruit_led_animation.animation import Animation
10+
from adafruit_led_animation.grid import PixelGrid, HORIZONTAL
11+
12+
13+
def _is_pixel_off(pixel):
14+
return pixel[0] == 0 and pixel[1] == 0 and pixel[2] == 0
15+
16+
17+
class ConwaysLifeAnimation(Animation):
18+
# Constants
19+
DIRECTION_OFFSETS = [
20+
(0, 1),
21+
(0, -1),
22+
(1, 0),
23+
(-1, 0),
24+
(1, 1),
25+
(-1, 1),
26+
(1, -1),
27+
(-1, -1),
28+
]
29+
LIVE = const(0x01)
30+
DEAD = const(0x00)
31+
32+
def __init__(
33+
self,
34+
pixel_object,
35+
speed,
36+
color,
37+
width,
38+
height,
39+
initial_cells,
40+
equilibrium_restart=True,
41+
):
42+
"""
43+
Conway's Game of Life implementation. Watch the cells
44+
live and die based on the classic rules.
45+
46+
:param pixel_object: The initialised LED object.
47+
:param float speed: Animation refresh rate in seconds, e.g. ``0.1``.
48+
:param color: the color to use for live cells
49+
:param width: the width of the grid
50+
:param height: the height of the grid
51+
:param initial_cells: list of initial cells to be live
52+
:param equilibrium_restart: whether to restart when the simulation gets stuck unchanging
53+
"""
54+
super().__init__(pixel_object, speed, color)
55+
56+
# list to hold which cells are live
57+
self.drawn_pixels = []
58+
59+
# store the initial cells
60+
self.initial_cells = initial_cells
61+
62+
# PixelGrid helper to access the strand as a 2D grid
63+
self.pixel_grid = PixelGrid(
64+
pixel_object, width, height, orientation=HORIZONTAL, alternating=False
65+
)
66+
67+
# size of the grid
68+
self.width = width
69+
self.height = height
70+
71+
# equilibrium restart boolean
72+
self.equilibrium_restart = equilibrium_restart
73+
74+
# counter to store how many turns since the last change
75+
self.equilibrium_turns = 0
76+
77+
# self._init_cells()
78+
79+
def _is_grid_empty(self):
80+
"""
81+
Checks if the grid is empty.
82+
83+
:return: True if there are no live cells, False otherwise
84+
"""
85+
for y in range(self.height):
86+
for x in range(self.width):
87+
if not _is_pixel_off(self.pixel_grid[x, y]):
88+
return False
89+
90+
return True
91+
92+
def _init_cells(self):
93+
"""
94+
Turn off all LEDs then turn on ones cooresponding to the initial_cells
95+
96+
:return: None
97+
"""
98+
self.pixel_grid.fill(0x000000)
99+
for cell in self.initial_cells:
100+
self.pixel_grid[cell] = self.color
101+
102+
def _count_neighbors(self, cell):
103+
"""
104+
Check how many live cell neighbors are found at the given location
105+
:param cell: the location to check
106+
:return: the number of live cell neighbors
107+
"""
108+
neighbors = 0
109+
for direction in ConwaysLifeAnimation.DIRECTION_OFFSETS:
110+
try:
111+
if not _is_pixel_off(
112+
self.pixel_grid[cell[0] + direction[0], cell[1] + direction[1]]
113+
):
114+
neighbors += 1
115+
except IndexError:
116+
pass
117+
return neighbors
118+
119+
def draw(self):
120+
# pylint: disable=too-many-branches
121+
"""
122+
draw the current frame of the animation
123+
124+
:return: None
125+
"""
126+
# if there are no live cells
127+
if self._is_grid_empty():
128+
# spawn the inital_cells and return
129+
self._init_cells()
130+
return
131+
132+
# list to hold locations to despawn live cells
133+
despawning_cells = []
134+
135+
# list to hold locations spawn new live cells
136+
spawning_cells = []
137+
138+
# loop over the grid
139+
for y in range(self.height):
140+
for x in range(self.width):
141+
142+
# check and set the current cell type, live or dead
143+
if _is_pixel_off(self.pixel_grid[x, y]):
144+
cur_cell_type = ConwaysLifeAnimation.DEAD
145+
else:
146+
cur_cell_type = ConwaysLifeAnimation.LIVE
147+
148+
# get a count of the neigbors
149+
neighbors = self._count_neighbors((x, y))
150+
151+
# if the current cell is alive
152+
if cur_cell_type == ConwaysLifeAnimation.LIVE:
153+
# if it has fewer than 2 neighbors
154+
if neighbors < 2:
155+
# add its location to the despawn list
156+
despawning_cells.append((x, y))
157+
158+
# if it has more than 3 neighbors
159+
if neighbors > 3:
160+
# add its location to the despawn list
161+
despawning_cells.append((x, y))
162+
163+
# if the current location is not a living cell
164+
elif cur_cell_type == ConwaysLifeAnimation.DEAD:
165+
# if it has exactly 3 neighbors
166+
if neighbors == 3:
167+
# add the current location to the spawn list
168+
spawning_cells.append((x, y))
169+
170+
# loop over the despawn locations
171+
for cell in despawning_cells:
172+
# turn off LEDs at each location
173+
self.pixel_grid[cell] = 0x000000
174+
175+
# loop over the spawn list
176+
for cell in spawning_cells:
177+
# turn on LEDs at each location
178+
self.pixel_grid[cell] = self.color
179+
180+
# if equilibrium restart mode is enabled
181+
if self.equilibrium_restart:
182+
# if there were no cells spawned or despaned this round
183+
if len(despawning_cells) == 0 and len(spawning_cells) == 0:
184+
# increment equilibrium turns counter
185+
self.equilibrium_turns += 1
186+
# if the counter is 3 or higher
187+
if self.equilibrium_turns >= 3:
188+
# go back to the initial_cells
189+
self._init_cells()
190+
191+
# reset the turns counter to zero
192+
self.equilibrium_turns = 0

0 commit comments

Comments
 (0)