Skip to content

Commit f327f68

Browse files
authored
Merge pull request #2998 from jepler/jammy-matrix
into the jammy matrix
2 parents 6c91cac + 9ebf8bc commit f327f68

File tree

2 files changed

+301
-0
lines changed

2 files changed

+301
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
// SPDX-FileCopyrightText: 2021 Anne Barela for Adafruit Industries
2+
//
3+
// SPDX-License-Identifier: MIT
4+
//
5+
// Based on Adafruit-DVI-HSTX library code written by Jeff Epler
6+
// and use of Claude 3.7 Sonnet on 3/2/2025
7+
// https://claude.site/artifacts/cf022b66-50c3-43eb-b334-17fbf0ed791c
8+
9+
#include <Adafruit_dvhstx.h>
10+
11+
// Display configuration for text mode in Adafruit-DVI-HSTX
12+
const int SCREEN_WIDTH = 91;
13+
const int SCREEN_HEIGHT = 30;
14+
15+
// Animation speed (lower = faster)
16+
// Adjust this value to change the speed of the animation
17+
const int ANIMATION_SPEED = 1; // milliseconds between updates
18+
19+
// Initialize display for Adafruit Metro RP2350
20+
DVHSTXText display(DVHSTX_PINOUT_DEFAULT, true); // Adafruit Metro HSTX Pinout
21+
22+
// Define structures for character streams
23+
struct CharStream {
24+
int x; // X position
25+
int y; // Y position (head of the stream)
26+
int length; // Length of the stream
27+
int speed; // How many frames to wait before moving
28+
int countdown; // Counter for movement
29+
int headColor;
30+
bool active; // Whether this stream is currently active
31+
bool canFreeze; // Whether this stream is currently active
32+
char chars[30]; // Characters in the stream
33+
};
34+
35+
// Array of character streams - increased for higher density
36+
// To fill 60-75% of the screen width (91 chars), we need around 55-68 active
37+
// streams
38+
CharStream streams[250]; // Allow for decent density
39+
40+
// Stream creation rate (higher = more frequent new streams)
41+
const int STREAM_CREATION_CHANCE =
42+
65; // % chance per frame to create new stream
43+
44+
// Initial streams to create at startup
45+
const int INITIAL_STREAMS = 30;
46+
47+
// Random characters that appear in the streams
48+
const char matrixChars[] =
49+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[]{}|;:,.<>?/\\";
50+
const int numMatrixChars = sizeof(matrixChars) - 1;
51+
52+
const char frozenLine1[] = " A D A F R U I T ";
53+
const char frozenLine2[] = " ";
54+
const char frozenLine3[] = "F R U I T J A M";
55+
56+
constexpr size_t nFrozen = sizeof(frozenLine1) - 1;
57+
const char *const frozenLines[] = {frozenLine1, frozenLine2, frozenLine3};
58+
const int colorFrozen[3] = {
59+
TextColor::TEXT_WHITE,
60+
TextColor::TEXT_WHITE,
61+
TextColor::TEXT_MAGENTA,
62+
};
63+
64+
constexpr auto frozenFirstRow = (SCREEN_HEIGHT / 2) - 1;
65+
constexpr auto frozenFirstCol = (SCREEN_WIDTH - nFrozen) / 2;
66+
bool isFrozen[3][nFrozen];
67+
68+
// Function declarations
69+
void initStreams();
70+
void updateStreams();
71+
void drawStream(CharStream &stream);
72+
int createNewStream();
73+
char getRandomChar();
74+
75+
void setup() {
76+
// Initialize the display
77+
display.begin();
78+
display.clear();
79+
80+
// Seed the random number generator
81+
randomSeed(analogRead(A0));
82+
83+
// Initialize all streams
84+
initStreams();
85+
86+
for (int fy = 0; fy < 3; fy++) {
87+
for (int fx = 0; fx < nFrozen; fx++) {
88+
int c = frozenLines[fy][fx];
89+
if (c == ' ')
90+
continue;
91+
isFrozen[fy][fx] = true;
92+
}
93+
}
94+
}
95+
96+
bool anyActiveStreams() {
97+
for (int i = 0; i < sizeof(streams) / sizeof(streams[0]); i++) {
98+
if (streams[i].active)
99+
return true;
100+
}
101+
return false;
102+
}
103+
bool allFrozen() {
104+
for (int fy = 0; fy < 3; fy++) {
105+
for (int fx = 0; fx < nFrozen; fx++) {
106+
int c = frozenLines[fy][fx];
107+
if (c == ' ')
108+
continue;
109+
if (!isFrozen[fy][fx])
110+
return false;
111+
}
112+
}
113+
return true;
114+
}
115+
116+
void loop() {
117+
// Update and draw all streams
118+
updateStreams();
119+
120+
// Randomly create new streams at a higher rate
121+
if (random(100) < STREAM_CREATION_CHANCE) {
122+
if (allFrozen()) {
123+
if (!anyActiveStreams()) {
124+
memset(isFrozen, 0, sizeof(isFrozen));
125+
for (int fy = 0; fy < 3; fy++) {
126+
for (int fx = 0; fx < nFrozen; fx++) {
127+
int c = frozenLines[fy][fx];
128+
if (c == ' ')
129+
continue;
130+
int st;
131+
if ((st = createNewStream()) != -1) {
132+
streams[st].x = fx + frozenFirstCol;
133+
streams[st].y = fy + frozenFirstRow;
134+
streams[st].chars[0] = c;
135+
for (int i = 1; i < streams[st].length; i++) {
136+
streams[st].chars[i] = ' ';
137+
}
138+
streams[st].canFreeze = false;
139+
streams[st].headColor = colorFrozen[fy];
140+
}
141+
}
142+
}
143+
}
144+
} else {
145+
createNewStream();
146+
}
147+
}
148+
149+
// Control animation speed
150+
delay(ANIMATION_SPEED);
151+
}
152+
153+
void initStreams() {
154+
// Initialize all streams as inactive
155+
for (int i = 0; i < sizeof(streams) / sizeof(streams[0]); i++) {
156+
streams[i].active = false;
157+
}
158+
159+
// Create more initial streams for immediate visual impact
160+
for (int i = 0; i < INITIAL_STREAMS; i++) {
161+
createNewStream();
162+
}
163+
}
164+
165+
int createNewStream() {
166+
// Find an inactive stream
167+
for (int i = 0; i < sizeof(streams) / sizeof(streams[0]); i++) {
168+
if (!streams[i].active) {
169+
// Initialize the stream
170+
streams[i].x = random(SCREEN_WIDTH);
171+
streams[i].y = random(5) - 5; // Start above the screen
172+
streams[i].length = random(5, 20);
173+
streams[i].speed = random(1, 4);
174+
streams[i].countdown = streams[i].speed;
175+
streams[i].active = true;
176+
streams[i].headColor = TextColor::TEXT_WHITE;
177+
streams[i].canFreeze = true;
178+
179+
// Fill with random characters
180+
for (int j = 0; j < streams[i].length; j++) {
181+
streams[i].chars[j] = getRandomChar();
182+
}
183+
184+
return i;
185+
}
186+
}
187+
return -1;
188+
}
189+
190+
void updateStreams() {
191+
display.clear();
192+
delay(30);
193+
194+
// Count active streams (for debugging if needed)
195+
int activeCount = 0;
196+
197+
for (int i = 0; i < sizeof(streams) / sizeof(streams[0]); i++) {
198+
if (streams[i].active) {
199+
activeCount++;
200+
streams[i].countdown--;
201+
202+
// Time to move the stream down
203+
if (streams[i].countdown <= 0) {
204+
streams[i].y++;
205+
streams[i].countdown = streams[i].speed;
206+
207+
// Change a random character in the stream
208+
int randomIndex = random(streams[i].length);
209+
streams[i].chars[randomIndex] = getRandomChar();
210+
}
211+
212+
// Draw the stream
213+
drawStream(streams[i]);
214+
215+
// Check if the stream has moved completely off the screen
216+
if (streams[i].y - streams[i].length > SCREEN_HEIGHT) {
217+
streams[i].active = false;
218+
}
219+
}
220+
}
221+
displayFrozen();
222+
display.swap();
223+
}
224+
225+
bool handleFrozen(int x, int y, int i, bool canFreeze) {
226+
if (x < frozenFirstCol || y < frozenFirstRow)
227+
return false;
228+
int fx = x - frozenFirstCol;
229+
int fy = y - frozenFirstRow;
230+
if (fy > 2 || fx >= nFrozen)
231+
return false;
232+
int c = frozenLines[fy][fx];
233+
if (c == ' ')
234+
return false;
235+
if (!isFrozen[fy][fx]) {
236+
if (canFreeze && random(5) == 0) {
237+
isFrozen[fy][fx] = true;
238+
} else {
239+
return false;
240+
}
241+
}
242+
243+
return true;
244+
}
245+
246+
void displayFrozen() {
247+
for (int fy = 0; fy < 3; fy++) {
248+
display.setColor(colorFrozen[fy]);
249+
for (int fx = 0; fx < nFrozen; fx++) {
250+
int x = fx + frozenFirstCol;
251+
int y = fy + frozenFirstRow;
252+
if (isFrozen[fy][fx]) {
253+
display.setCursor(x, y);
254+
display.write(frozenLines[fy][fx]);
255+
}
256+
}
257+
}
258+
}
259+
260+
void drawStream(CharStream &stream) {
261+
for (int i = 0; i < stream.length; i++) {
262+
int y = stream.y - i;
263+
int x = stream.x;
264+
// Only draw if the character is on screen
265+
if (y >= 0 && y < SCREEN_HEIGHT) {
266+
display.setCursor(x, y);
267+
268+
// Set different colors/intensities based on position in the stream
269+
if (i == 0) {
270+
// Head of the stream is white (brightest)
271+
display.setColor(stream.headColor);
272+
} else if (i < 3) {
273+
// First few characters are bright green
274+
display.setColor(TextColor::TEXT_GREEN, TextColor::BG_BLACK,
275+
TextColor::ATTR_NORMAL_INTEN);
276+
} else if (i < 6) {
277+
// Next few are medium green
278+
display.setColor(TextColor::TEXT_GREEN, TextColor::BG_BLACK,
279+
TextColor::ATTR_LOW_INTEN);
280+
} else {
281+
// The rest are dim green
282+
display.setColor(TextColor::TEXT_GREEN, TextColor::BG_BLACK,
283+
TextColor::ATTR_V_LOW_INTEN);
284+
}
285+
286+
// Draw the character
287+
if (!handleFrozen(x, y, i, stream.canFreeze)) {
288+
display.write(stream.chars[i]);
289+
}
290+
}
291+
}
292+
293+
// Occasionally change a character in the stream
294+
if (random(100) < 25) { // 25% chance
295+
int idx = random(stream.length);
296+
stream.chars[idx] = getRandomChar();
297+
}
298+
}
299+
300+
char getRandomChar() { return matrixChars[random(numMatrixChars)]; }

0 commit comments

Comments
 (0)