Framework-LED-Matrix 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli.py +669 -0
- framework_led_matrix/__init__.py +0 -0
- framework_led_matrix/apps/__init__.py +0 -0
- framework_led_matrix/apps/background_runner.py +125 -0
- framework_led_matrix/apps/runtime.py +185 -0
- framework_led_matrix/core/__init__.py +0 -0
- framework_led_matrix/core/led_commands.py +406 -0
- framework_led_matrix/core/math_engine.py +294 -0
- framework_led_matrix/simulations/BihamMiddletonLevineTrafficModel.py +238 -0
- framework_led_matrix/simulations/HardyPomeauPazzis.py +241 -0
- framework_led_matrix/simulations/__init__.py +0 -0
- framework_led_matrix/simulations/inner_totalistic.py +47 -0
- framework_led_matrix/simulations/outer_totalistic.py +112 -0
- framework_led_matrix/utils/__init__.py +0 -0
- framework_led_matrix/utils/anagrams.py +39 -0
- framework_led_matrix/utils/text_rendering.py +281 -0
- framework_led_matrix-0.1.1.dist-info/METADATA +159 -0
- framework_led_matrix-0.1.1.dist-info/RECORD +22 -0
- framework_led_matrix-0.1.1.dist-info/WHEEL +5 -0
- framework_led_matrix-0.1.1.dist-info/entry_points.txt +2 -0
- framework_led_matrix-0.1.1.dist-info/licenses/LICENSE +674 -0
- framework_led_matrix-0.1.1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
hpp_lga_model.py
|
|
5
|
+
|
|
6
|
+
Implements the HPP (Hardy-Pomeau-Pazzis) Lattice Gas Automaton (LGA)
|
|
7
|
+
for the 34x9 LED matrix, using the cellpylib library.
|
|
8
|
+
|
|
9
|
+
This automaton models fluid dynamics using 4-bit particle states
|
|
10
|
+
and specific collision/propagation rules. It is NOT a totalistic
|
|
11
|
+
automaton and requires a custom 'apply_rule' function.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import cellpylib as cpl
|
|
15
|
+
import numpy as np
|
|
16
|
+
import time
|
|
17
|
+
import random
|
|
18
|
+
from typing import List, Optional
|
|
19
|
+
from framework_led_matrix.core.led_commands import log, clear_graph, WIDTH, HEIGHT, draw_matrix_on_board, reset_modules
|
|
20
|
+
|
|
21
|
+
# --- HPP Particle States (Bitmasks) ---
|
|
22
|
+
# A cell's state is the bitwise OR of the particles it contains.
|
|
23
|
+
EMPTY = 0b0000 # 0
|
|
24
|
+
W_PARTICLE = 0b0001 # 1 (West-moving)
|
|
25
|
+
E_PARTICLE = 0b0010 # 2 (East-moving)
|
|
26
|
+
S_PARTICLE = 0b0100 # 4 (South-moving)
|
|
27
|
+
N_PARTICLE = 0b1000 # 8 (North-moving)
|
|
28
|
+
|
|
29
|
+
# --- HPP Collision States ---
|
|
30
|
+
# These are the only two states that result in a collision
|
|
31
|
+
WE_COLLIDE = W_PARTICLE | E_PARTICLE # 3 (West + East)
|
|
32
|
+
NS_COLLIDE = N_PARTICLE | S_PARTICLE # 12 (North + South)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def draw_hpp_board(board: List[List[int]], which: str):
|
|
36
|
+
"""
|
|
37
|
+
Converts the 16-state HPP board to a regular matrix
|
|
38
|
+
(0-1) and draws it.
|
|
39
|
+
"""
|
|
40
|
+
matrix = [[0 for _ in range(WIDTH)] for _ in range(HEIGHT)]
|
|
41
|
+
for r in range(HEIGHT):
|
|
42
|
+
for c in range(WIDTH):
|
|
43
|
+
state = board[r][c]
|
|
44
|
+
if state != EMPTY:
|
|
45
|
+
matrix[r][c] = 1 # Occupied
|
|
46
|
+
else:
|
|
47
|
+
matrix[r][c] = 0 # Empty
|
|
48
|
+
draw_matrix_on_board(matrix, which)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def hpp_collide(state: int) -> int:
|
|
52
|
+
"""
|
|
53
|
+
Computes the post-collision state for a single HPP cell.
|
|
54
|
+
This is the first half of the HPP rule.
|
|
55
|
+
"""
|
|
56
|
+
# N+S collision (12)
|
|
57
|
+
if state == NS_COLLIDE:
|
|
58
|
+
return WE_COLLIDE # Becomes E+W (3)
|
|
59
|
+
|
|
60
|
+
# E+W collision (3)
|
|
61
|
+
if state == WE_COLLIDE:
|
|
62
|
+
return NS_COLLIDE # Becomes N+S (12)
|
|
63
|
+
|
|
64
|
+
# All other states (0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15)
|
|
65
|
+
# pass through unchanged.
|
|
66
|
+
return state
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def hpp_lga_rule(neighbourhood: np.ndarray, c_coord: tuple, t: int) -> int:
|
|
70
|
+
"""
|
|
71
|
+
The HPP rule function for cellpylib's evolve2d.
|
|
72
|
+
This is the "Propagation" step. It calculates the new state of
|
|
73
|
+
the center cell (1,1) by "gathering" all particles that will
|
|
74
|
+
propagate *into* it from its 4 neighbors' *post-collision* states.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
neighbourhood: The 2D (3x3) NumPy array (Moore neighborhood).
|
|
78
|
+
c_coord (tuple): The (row, col) coordinate (unused).
|
|
79
|
+
t (int): The current timestep (unused).
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
# 1. Get the pre-collision state of the 4 neighbors we care about.
|
|
83
|
+
north_cell_pre_collide = neighbourhood[0, 1]
|
|
84
|
+
south_cell_pre_collide = neighbourhood[2, 1]
|
|
85
|
+
east_cell_pre_collide = neighbourhood[1, 2]
|
|
86
|
+
west_cell_pre_collide = neighbourhood[1, 0]
|
|
87
|
+
|
|
88
|
+
# 2. Compute the post-collision state for each neighbor.
|
|
89
|
+
# This determines what particles are *available to move*
|
|
90
|
+
n_post_collide = hpp_collide(north_cell_pre_collide)
|
|
91
|
+
s_post_collide = hpp_collide(south_cell_pre_collide)
|
|
92
|
+
e_post_collide = hpp_collide(east_cell_pre_collide)
|
|
93
|
+
w_post_collide = hpp_collide(west_cell_pre_collide)
|
|
94
|
+
|
|
95
|
+
# 3. "Gather" the particles that will arrive at the center cell.
|
|
96
|
+
|
|
97
|
+
# The new North particle comes from the South neighbor's post-collision North particle.
|
|
98
|
+
from_south = s_post_collide & N_PARTICLE
|
|
99
|
+
|
|
100
|
+
# The new South particle comes from the North neighbor's post-collision South particle.
|
|
101
|
+
from_north = n_post_collide & S_PARTICLE
|
|
102
|
+
|
|
103
|
+
# The new West particle comes from the East neighbor's post-collision West particle.
|
|
104
|
+
from_east = e_post_collide & W_PARTICLE
|
|
105
|
+
|
|
106
|
+
# The new East particle comes from the West neighbor's post-collision East particle.
|
|
107
|
+
from_west = w_post_collide & E_PARTICLE
|
|
108
|
+
|
|
109
|
+
# The new state of the center cell is the bitwise OR
|
|
110
|
+
# of all particles that have propagated into it.
|
|
111
|
+
new_center_state = from_north | from_south | from_east | from_west
|
|
112
|
+
|
|
113
|
+
return new_center_state
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def create_hpp_board_np(density: float = 0.5, initial_state: Optional[np.ndarray] = None) -> np.ndarray:
|
|
117
|
+
"""
|
|
118
|
+
Creates a new random board for the HPP model, or uses a provided one.
|
|
119
|
+
|
|
120
|
+
If initial_state is provided, it is used directly.
|
|
121
|
+
If initial_state is None, a new random board is generated
|
|
122
|
+
based on the density.
|
|
123
|
+
"""
|
|
124
|
+
if initial_state is not None:
|
|
125
|
+
#take initial board, provide random particles movements
|
|
126
|
+
for r in range(HEIGHT):
|
|
127
|
+
for c in range(WIDTH):
|
|
128
|
+
if initial_state[r, c] != EMPTY:
|
|
129
|
+
#random particle type to the occupied cell
|
|
130
|
+
initial_state[r, c] = random.choice([W_PARTICLE, E_PARTICLE, S_PARTICLE, N_PARTICLE])
|
|
131
|
+
return initial_state
|
|
132
|
+
|
|
133
|
+
log(f"HPP: Creating new NumPy board with particle density {density}")
|
|
134
|
+
board = np.full((HEIGHT, WIDTH), EMPTY, dtype=int)
|
|
135
|
+
|
|
136
|
+
total_cells = WIDTH * HEIGHT
|
|
137
|
+
num_particles = int(total_cells * density)
|
|
138
|
+
|
|
139
|
+
# Get a list of all possible single-particle states
|
|
140
|
+
particle_types = [W_PARTICLE, E_PARTICLE, S_PARTICLE, N_PARTICLE]
|
|
141
|
+
|
|
142
|
+
# Get a list of unique cells to populate
|
|
143
|
+
cells_to_populate = random.sample(
|
|
144
|
+
[(r, c) for r in range(HEIGHT) for c in range(WIDTH)],
|
|
145
|
+
num_particles
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
for r, c in cells_to_populate:
|
|
149
|
+
# Assign a random particle type to the chosen cell
|
|
150
|
+
board[r, c] = random.choice(particle_types)
|
|
151
|
+
|
|
152
|
+
log(f"HPP: Seeded board with {num_particles} random particles.")
|
|
153
|
+
return board
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def run_hpp_simulation(
|
|
157
|
+
initial_state: Optional[np.ndarray] = None,
|
|
158
|
+
density: float = 0.5,
|
|
159
|
+
timesteps: int = 500,
|
|
160
|
+
delay_sec: float = 0.1,
|
|
161
|
+
which: str = 'both'
|
|
162
|
+
):
|
|
163
|
+
"""
|
|
164
|
+
Runs the HPP Lattice Gas Automaton simulation using cellpylib.
|
|
165
|
+
"""
|
|
166
|
+
log(f"HPP: Starting simulation. density={density}, steps={timesteps}")
|
|
167
|
+
|
|
168
|
+
# Use the create function, which respects the initial_state override
|
|
169
|
+
initial_state_2d = create_hpp_board_np(density, initial_state)
|
|
170
|
+
|
|
171
|
+
# Wrap in 3D array for cellpylib's evolve2d function
|
|
172
|
+
initial_state_3d = np.array([initial_state_2d])
|
|
173
|
+
|
|
174
|
+
log(f"HPP: Evolving {timesteps} steps...")
|
|
175
|
+
|
|
176
|
+
# Evolve the cellular automaton
|
|
177
|
+
all_generations = cpl.evolve2d(
|
|
178
|
+
cellular_automaton=initial_state_3d,
|
|
179
|
+
timesteps=timesteps,
|
|
180
|
+
neighbourhood='Moore', # We need N,S,E,W neighbors
|
|
181
|
+
apply_rule=hpp_lga_rule,
|
|
182
|
+
r=1 # Radius 1
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
log(f"HPP: Evolution complete. Result shape: {all_generations.shape}")
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
stable_counter = 0
|
|
189
|
+
total_frames = all_generations.shape[0]
|
|
190
|
+
|
|
191
|
+
for i in range(total_frames):
|
|
192
|
+
current_board_np = all_generations[i]
|
|
193
|
+
current_board_list = current_board_np.tolist()
|
|
194
|
+
|
|
195
|
+
# Draw the state to the LED matrix
|
|
196
|
+
draw_hpp_board(current_board_list, which)
|
|
197
|
+
time.sleep(delay_sec)
|
|
198
|
+
|
|
199
|
+
if i % 20 == 0 or i == total_frames - 1:
|
|
200
|
+
log(f"HPP: Step {i}/{timesteps}.")
|
|
201
|
+
|
|
202
|
+
# Check for stable state (gridlock or empty)
|
|
203
|
+
if i > 0:
|
|
204
|
+
if np.array_equal(current_board_np, all_generations[i-1]):
|
|
205
|
+
stable_counter += 1
|
|
206
|
+
else:
|
|
207
|
+
stable_counter = 0
|
|
208
|
+
|
|
209
|
+
if stable_counter >= 20:
|
|
210
|
+
log("HPP: State stable for 20 steps. Halting.")
|
|
211
|
+
time.sleep(2)
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
except KeyboardInterrupt:
|
|
215
|
+
log("HPP: KeyboardInterrupt received, stopping.")
|
|
216
|
+
finally:
|
|
217
|
+
log(f"HPP: simulation finished.")
|
|
218
|
+
clear_graph()
|
|
219
|
+
log("HPP: cleared display.")
|
|
220
|
+
|
|
221
|
+
def run_test_hpp():
|
|
222
|
+
"""
|
|
223
|
+
Test function to run a sample HPP simulation.
|
|
224
|
+
"""
|
|
225
|
+
matrix = [[0 for _ in range(WIDTH)] for _ in range(HEIGHT)]
|
|
226
|
+
matrix[HEIGHT // 2][WIDTH // 2] = N_PARTICLE | S_PARTICLE
|
|
227
|
+
matrix[HEIGHT // 2][(WIDTH // 2) - 1] = E_PARTICLE | W_PARTICLE
|
|
228
|
+
board = create_hpp_board_np(initial_state=np.array(matrix))
|
|
229
|
+
try:
|
|
230
|
+
run_hpp_simulation(
|
|
231
|
+
initial_state=board,
|
|
232
|
+
density=0.3,
|
|
233
|
+
timesteps=200,
|
|
234
|
+
delay_sec=0.05,
|
|
235
|
+
which='both'
|
|
236
|
+
)
|
|
237
|
+
finally:
|
|
238
|
+
reset_modules()
|
|
239
|
+
|
|
240
|
+
if __name__ == "__main__":
|
|
241
|
+
run_test_hpp()
|
|
File without changes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import cellpylib as cpl
|
|
3
|
+
from framework_led_matrix.core.led_commands import log, WIDTH, HEIGHT
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def create_simple_state(k: int = 2, val: int = 1) -> np.ndarray:
|
|
8
|
+
"""Creates a 2D (HEIGHT, WIDTH) array of zeros with a single 'val' at the center."""
|
|
9
|
+
log(f"CPL Helpers: Creating simple center-seed state (val={val})")
|
|
10
|
+
board = np.zeros((HEIGHT, WIDTH), dtype=int)
|
|
11
|
+
board[HEIGHT // 2, WIDTH // 2] = val
|
|
12
|
+
return board
|
|
13
|
+
|
|
14
|
+
def run_totalistic_ca(initial_state: np.ndarray, timesteps: int, rule_number: int) -> np.ndarray:
|
|
15
|
+
"""
|
|
16
|
+
Runs a k=2 (binary) totalistic CA for 'timesteps'.
|
|
17
|
+
|
|
18
|
+
The rule is based on the *sum* of the 8 'Moore' neighbors + center.
|
|
19
|
+
'rule_number' is the NKS-style rule number for k=2.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
A 3D NumPy array of shape (timesteps + 1, HEIGHT, WIDTH)
|
|
23
|
+
"""
|
|
24
|
+
k = 2
|
|
25
|
+
log(f"Totalistic CA (k=2): Running (rule={rule_number}) for {timesteps} steps.")
|
|
26
|
+
|
|
27
|
+
rule_func = lambda n, c_coord, t: cpl.totalistic_rule(n, k=k, rule=rule_number)
|
|
28
|
+
|
|
29
|
+
initial_state_3d = np.array([initial_state])
|
|
30
|
+
|
|
31
|
+
all_generations = cpl.evolve2d(
|
|
32
|
+
cellular_automaton=initial_state_3d,
|
|
33
|
+
timesteps=timesteps,
|
|
34
|
+
neighbourhood='Moore',
|
|
35
|
+
apply_rule=rule_func
|
|
36
|
+
)
|
|
37
|
+
return all_generations
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
# Test block
|
|
42
|
+
log("--- Testing Totalistic CA ---")
|
|
43
|
+
init_state = create_simple_state(k=3, val=1)
|
|
44
|
+
# Rule 777 is a 3-color replicator
|
|
45
|
+
history = run_totalistic_ca(init_state, timesteps=50, rule_number=777)
|
|
46
|
+
log(f"History shape: {history.shape}")
|
|
47
|
+
log("Test complete.")
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import cellpylib as cpl
|
|
3
|
+
from framework_led_matrix.core.led_commands import log, WIDTH, HEIGHT, coordinates_to_matrix
|
|
4
|
+
|
|
5
|
+
STARTING_STATES_GOF = {
|
|
6
|
+
"blinker": coordinates_to_matrix([[17, 3], [17, 4], [17, 5]]),
|
|
7
|
+
"toad": coordinates_to_matrix([[17, 3], [17, 4], [17, 5], [18, 2], [18, 3], [18, 4]]),
|
|
8
|
+
"pentadecathlon": coordinates_to_matrix([
|
|
9
|
+
[12, 4], [13, 4], [14, 2], [14, 4], [14, 6], [15, 4], [16, 4],
|
|
10
|
+
[17, 4], [18, 4], [19, 2], [19, 4], [19, 6], [20, 4], [21, 4]
|
|
11
|
+
]),
|
|
12
|
+
"glider": coordinates_to_matrix([[1, 2], [2, 3], [3, 1], [3, 2], [3, 3]]),
|
|
13
|
+
"lwss": coordinates_to_matrix([
|
|
14
|
+
[17, 3], [17, 5], [18, 2], [19, 2], [19, 5],
|
|
15
|
+
[20, 2], [20, 3], [20, 4], [20, 5]
|
|
16
|
+
]),
|
|
17
|
+
"r_pentomino": coordinates_to_matrix([[17, 4], [17, 5], [18, 3], [18, 4], [19, 4]]),
|
|
18
|
+
"diehard": coordinates_to_matrix([
|
|
19
|
+
[17, 7], [18, 1], [18, 2], [19, 2], [19, 5], [19, 6], [19, 7]
|
|
20
|
+
]),
|
|
21
|
+
"acorn": coordinates_to_matrix([
|
|
22
|
+
[17, 2], [18, 4], [19, 1], [19, 2], [19, 5], [19, 6], [19, 7]
|
|
23
|
+
]),
|
|
24
|
+
"block": coordinates_to_matrix([[17, 3], [17, 4], [18, 3], [18, 4]]),
|
|
25
|
+
"beehive": coordinates_to_matrix([[17, 3], [17, 4], [18, 2], [18, 5], [19, 3], [19, 4]]),
|
|
26
|
+
"r_pentomino": coordinates_to_matrix([[17, 4], [17, 5], [18, 3], [18, 4], [19, 4]]),
|
|
27
|
+
"rabbit": coordinates_to_matrix([[17, 3], [17, 4], [18, 2], [18, 5], [19, 5], [20, 5]])
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
game_of_life_rules = {
|
|
32
|
+
'Original': {'B': [3], 'S': [2, 3]},
|
|
33
|
+
'HighLife': {'B': [3, 6], 'S': [2, 3]},
|
|
34
|
+
'Day & Night': {'B': [3, 6, 7, 8], 'S': [3, 4, 6, 7, 8]},
|
|
35
|
+
'Seeds': {'B': [2], 'S': []},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
NAMED_RULES = {
|
|
39
|
+
"Life": ([3], [2, 3]),
|
|
40
|
+
"HighLife": ([3, 6], [2, 3]),
|
|
41
|
+
"Day & Night": ([3, 6, 7, 8], [3, 4, 6, 7, 8]),
|
|
42
|
+
"Seeds": ([2], []),
|
|
43
|
+
"Maze": ([3], [1, 2, 3, 4, 5]),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def run_outer_totalistic_ca(
|
|
48
|
+
initial_state: np.ndarray,
|
|
49
|
+
timesteps: int,
|
|
50
|
+
b_rule: list[int],
|
|
51
|
+
s_rule: list[int]
|
|
52
|
+
) -> np.ndarray:
|
|
53
|
+
"""
|
|
54
|
+
Runs a general binary Outer-Totalistic (B/S) CA for 'timesteps'.
|
|
55
|
+
This is the system used by Conway's Game of Life.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
initial_state: The 2D (H, W) NumPy array to start with.
|
|
59
|
+
timesteps: The number of generations to evolve.
|
|
60
|
+
b_rule: A list of neighbor counts to "Birth" a dead cell (e.g., [3]).
|
|
61
|
+
s_rule: A list of neighbor counts to "Survive" a live cell (e.g., [2, 3]).
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
A 3D NumPy array of shape (timesteps + 1, HEIGHT, WIDTH)
|
|
65
|
+
"""
|
|
66
|
+
log(f"Outer-Totalistic CA: Running B{b_rule}/S{s_rule} for {timesteps} steps.")
|
|
67
|
+
|
|
68
|
+
# Use sets for fast 'in' lookups
|
|
69
|
+
b_set = set(b_rule)
|
|
70
|
+
s_set = set(s_rule)
|
|
71
|
+
|
|
72
|
+
def outer_totalistic_rule(neighbourhood: np.ndarray, c_coord: tuple, t: int) -> int:
|
|
73
|
+
"""
|
|
74
|
+
The custom 'apply_rule' function that implements B/S logic.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
neighbourhood: The 2D (3x3) NumPy array (Moore neighborhood).
|
|
78
|
+
c_coord (tuple): The (row, col) coordinate (unused).
|
|
79
|
+
t (int): The current timestep (unused).
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
# Get the 8-neighbor sum (total sum - center cell)
|
|
83
|
+
neighbor_sum = np.sum(neighbourhood) - neighbourhood[1, 1]
|
|
84
|
+
|
|
85
|
+
# Get the center cell's current state
|
|
86
|
+
center_val = neighbourhood[1, 1]
|
|
87
|
+
|
|
88
|
+
if center_val == 1:
|
|
89
|
+
# Cell is ALIVE, check SURVIVAL rule
|
|
90
|
+
if neighbor_sum in s_set:
|
|
91
|
+
return 1 # Survive
|
|
92
|
+
else:
|
|
93
|
+
return 0 # Die
|
|
94
|
+
else:
|
|
95
|
+
# Cell is DEAD, check BIRTH rule
|
|
96
|
+
if neighbor_sum in b_set:
|
|
97
|
+
return 1 # Born
|
|
98
|
+
else:
|
|
99
|
+
return 0 # Stay dead
|
|
100
|
+
|
|
101
|
+
# Wrap initial state into a 3D array (shape [1, H, W])
|
|
102
|
+
initial_state_3d = np.array([initial_state])
|
|
103
|
+
|
|
104
|
+
# Evolve and return the history
|
|
105
|
+
all_generations = cpl.evolve2d(
|
|
106
|
+
cellular_automaton=initial_state_3d,
|
|
107
|
+
timesteps=timesteps,
|
|
108
|
+
neighbourhood='Moore', # B/S rules require the 8-neighbor Moore
|
|
109
|
+
apply_rule=outer_totalistic_rule
|
|
110
|
+
)
|
|
111
|
+
return all_generations
|
|
112
|
+
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from framework_led_matrix.simulations.outer_totalistic import run_outer_totalistic_ca
|
|
2
|
+
from framework_led_matrix.core.led_commands import start_animation, stop_animation, reset_modules, log
|
|
3
|
+
from framework_led_matrix.utils.text_rendering import draw_text_vertical
|
|
4
|
+
import nltk
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
def ensure_nltk_words():
|
|
8
|
+
"""Ensures the 'words' corpus is downloaded."""
|
|
9
|
+
try:
|
|
10
|
+
nltk.data.find('corpora/words')
|
|
11
|
+
except (LookupError, AttributeError):
|
|
12
|
+
log("NLTK 'words' corpus not found. Downloading...")
|
|
13
|
+
nltk.download('words', quiet=True)
|
|
14
|
+
|
|
15
|
+
def draw_anagram_on_matrix(word: str, which: str = 'both', animate: bool = True):
|
|
16
|
+
"""Draws an anagram of the given word on the LED matrix."""
|
|
17
|
+
log(f"draw_anagram_on_matrix: start word='{word}' which={which}")
|
|
18
|
+
ensure_nltk_words()
|
|
19
|
+
ana_lst = anagrams(word)
|
|
20
|
+
ana_lst.add(word)
|
|
21
|
+
ana_lst = list(ana_lst)
|
|
22
|
+
log(f"draw_anagram_on_matrix: will render {len(ana_lst)} strings: {ana_lst}")
|
|
23
|
+
for w in ana_lst:
|
|
24
|
+
log(f"draw_anagram_on_matrix: rendering '{w}' on matrix (vertical)")
|
|
25
|
+
draw_text_vertical(w, which=which)
|
|
26
|
+
log(f"draw_anagram_on_matrix: rendered '{w}', toggling animation briefly")
|
|
27
|
+
stop_animation()
|
|
28
|
+
time.sleep(2)
|
|
29
|
+
start_animation() if animate else stop_animation()
|
|
30
|
+
time.sleep(5)
|
|
31
|
+
log("draw_anagram_on_matrix: finished rendering all anagrams, resetting modules")
|
|
32
|
+
reset_modules()
|
|
33
|
+
|
|
34
|
+
def anagrams(word):
|
|
35
|
+
log(f"anagrams: finding anagrams for '{word}'")
|
|
36
|
+
word_sorted = sorted(word)
|
|
37
|
+
result = set(w for w in words.words() if sorted(w) == word_sorted)
|
|
38
|
+
log(f"anagrams: found {len(result)} candidates")
|
|
39
|
+
return result
|