#include #include "config.h" #include "hub75_driver.h" // ==================== Game of Life Configuration ==================== // 1 logical cell = 1 pixel on the matrix #define CELL_W TOTAL_WIDTH // 64 #define CELL_H TOTAL_HEIGHT // 64 // Grids for current and next generation (Tracks age: 0 = dead, >0 = alive) static uint8_t current[CELL_W][CELL_H]; static uint8_t next[CELL_W][CELL_H]; static unsigned long generation = 0; static unsigned long last_changed_gen = 0; // Tracks stagnation volatile bool engine_ready = false; // ==================== The Pattern Jukebox Library ==================== // Paste your RLE strings here! The engine will pick from them randomly. // To add more, just add a comma and paste the next string in quotes. // ==================== The Pattern Jukebox Library (50 Patterns) ==================== // A massive collection of Methuselahs, Spaceships, Oscillators, and Still Lifes. const char* const PATTERN_LIBRARY[] = { // --- METHUSELAHS (Exploders) --- "bo5b$3bo3b$2o2b3o!", // 0: The Acorn (5200+ generations) "6bo$2o$bo3b3o!", // 1: Diehard (Vanishes after 130) "b2o$2o$bo!", // 2: R-Pentomino (1100+ generations) "3o$obo$obo!", // 3: Pi-heptomino "o2b$2o2b$obo$2bo!", // 4: B-heptomino "b2o$2ob$bo!", // 5: F-pentomino "3ob$bobo$b2ob$2b2o!", // 6: Century "b3o$3ob$b2o!", // 7: C-heptomino "bo$o2b$b2o!", // 8: W-pentomino "o$3o$obo$2bo!", // 9: Herschel "o$o$3o!", // 10: V-pentomino "2o$b2o$2b2o!", // 11: Z-hexomino "2b2o$o2b2o$b3o!", // 12: Bunnies "o$2o$b3o$3bo!", // 13: Switch Engine precursor // --- SPACESHIPS (Travelers) --- "bob$2bo$3o!", // 14: Glider "bo2bo$o4b$o3bo$4o!", // 15: Lightweight Spaceship (LWSS) "3bobo$o5b$o4bo$5o!", // 16: Middleweight Spaceship (MWSS) "3b2obo$o6b$o5bo$6o!", // 17: Heavyweight Spaceship (HWSS) "bobo$o$bo$o3bo$2ob2o!", // 18: Copperhead "b2o$o2bo$bobo$2bo!", // 19: Loafer precursor // --- GUNS & FACTORIES --- "24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4bobo$10bo5bo7bo$11bo3bo$12b2o!", // 20: Gosper Glider Gun // --- OSCILLATORS (Looping Animations) --- "3o!", // 21: Blinker (Period 2) "b3o$3o!", // 22: Toad (Period 2) "2o2b$o3b$3bo$2b2o!", // 23: Beacon (Period 2) "2bo$o2bo$obo$2bo!", // 24: Clock (Period 2) "2bo4bo2b$2ob4ob2o$2bo4bo2b!", // 25: Pentadecathlon (Period 15) "2b3o3b3o$b$o4bobo4bo$o4bobo4bo$o4bobo4bo$2b3o3b3o$b$2b3o3b3o$o4bobo4bo$o4bobo4bo$o4bobo4bo$b$2b3o3b3o!", // 26: Pulsar (Period 3) "2o$2o$2b2o$2b2o!", // 27: Figure Eight (Period 8) "b2ob2o$b2ob2o$ob2obo$o4bo$o4bo!", // 28: Tumbler (Period 14) "2bo$obob$bob2o$2bo!", // 29: Clock 2 "3b2o$2bo2bo$bo4bo$o6bo$o6bo$bo4bo$2bo2bo$3b2o!", // 30: Octagon 2 (Period 5) "2b2o$b2ob2o$o4bo$b2ob2o$2b2o!", // 31: Honey Farm "2o$obobo$2o$2o$obobo$2o!", // 32: Spark Coil // --- STILL LIFES (Stable Structures) --- "2o$2o!", // 33: Block "b2ob$o2bo$b2ob!", // 34: Beehive "b2ob$o2bo$bobo$2bo!", // 35: Loaf "2ob$obo$b2o!", // 36: Boat "bob$obo$bob!", // 37: Tub "2o$obo$2o!", // 38: Ship "bo$obo$obo$bo!", // 39: Barge "bo$obo$obo$b2o!", // 40: Long boat "bo$obo$o2bo$b2o!", // 41: Mango "2o$o2bo$o2bo$2o!", // 42: Pond "2o$obo$2bo$2b2o!", // 43: Eater 1 "3o$o$b2o$2b2o!", // 44: Eater 2 "2o$obo$b2o!", // 45: Snake "2o$o$b2o!", // 46: Carrier "2o$o2bo$2b2o!", // 47: Aircraft carrier "2o$o2bo$2o!", // 48: Elevener "2o$o$3o!" // 49: Schmoo }; // Calculates exactly how many patterns are in your library dynamically const int NUM_PATTERNS = sizeof(PATTERN_LIBRARY) / sizeof(PATTERN_LIBRARY[0]); // ==================== Pattern Spawners ==================== // Clears the board completely void clear_board() { for (int y = 0; y < CELL_H; y++) { for (int x = 0; x < CELL_W; x++) { current[x][y] = 0; next[x][y] = 0; } } generation = 0; last_changed_gen = 0; } // Safely sets a cell to alive, preventing crashes if patterns hit the boundary void safe_spawn(int x, int y) { if (x >= 0 && x < CELL_W && y >= 0 && y < CELL_H) { current[x][y] = 1; } } // ==================== The RLE Parser ==================== // Reads the compressed text strings from the LifeWiki into actual matrix coordinates void spawn_from_rle(int start_x, int start_y, const char* rle) { int x = 0, y = 0, count = 0; while (*rle) { if (*rle >= '0' && *rle <= '9') { count = count * 10 + (*rle - '0'); } else if (*rle == 'b') { // Dead cell if (count == 0) count = 1; x += count; count = 0; } else if (*rle == 'o') { // Live cell if (count == 0) count = 1; for (int i = 0; i < count; i++) { safe_spawn(start_x + x, start_y + y); x++; } count = 0; } else if (*rle == '$') { // New row if (count == 0) count = 1; y += count; x = 0; count = 0; } else if (*rle == '!') { // End of pattern break; } rle++; } } // ==================== 50 Random Spawner ==================== // Sprinkles a precise number of completely random pixels (Mini Soup) void spawn_random_soup(int num_cells) { for (int i = 0; i < num_cells; i++) { int rx = random(0, CELL_W); int ry = random(0, CELL_H); safe_spawn(rx, ry); } } // ==================== The Jukebox Engine ==================== // Randomly decides how to repopulate the board when it stagnates void run_jukebox_spawner() { clear_board(); // 33% chance to spawn 60-75 random pixels, 66% chance to use the RLE library if (random(100) < 33) { // Mode 1: Pure random soup (60 to 75 cells) int cell_count = random(60, 76); spawn_random_soup(cell_count); } else { // Mode 2: Spawn 1 to 3 famous patterns from our library at random spots int patterns_to_drop = random(1, 4); for (int i = 0; i < patterns_to_drop; i++) { int selected_pattern = random(0, NUM_PATTERNS); // Keep spawns mostly on-screen by padding the random coordinates int spawn_x = random(5, CELL_W - 15); int spawn_y = random(5, CELL_H - 15); spawn_from_rle(spawn_x, spawn_y, PATTERN_LIBRARY[selected_pattern]); } } } // ==================== Helper: Count live neighbors (Hard Boundaries) ==================== int count_neighbors(int x, int y) { int live = 0; for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { if (dx == 0 && dy == 0) continue; int nx = x + dx; int ny = y + dy; // Hard boundaries: Only count if the neighbor is physically on the matrix if (nx >= 0 && nx < CELL_W && ny >= 0 && ny < CELL_H) { if (current[nx][ny] > 0) live++; } } } return live; } // We will use this global variable to track if the screen goes totally black int current_population = 0; // ==================== Compute next generation ==================== bool compute_next_generation() { bool board_changed = false; current_population = 0; // Reset the counter for this frame for (int y = 0; y < CELL_H; y++) { for (int x = 0; x < CELL_W; x++) { int neighbors = count_neighbors(x, y); uint8_t age = current[x][y]; if (age > 0) { // Survival if (neighbors == 2 || neighbors == 3) { next[x][y] = (age < 255) ? age + 1 : 255; if (age != next[x][y]) board_changed = true; } else { // Death next[x][y] = 0; board_changed = true; } } else { // Birth if (neighbors == 3) { next[x][y] = 1; board_changed = true; } else { next[x][y] = 0; } } // Count how many are alive for the instant-reset check! if (next[x][y] > 0) { current_population++; } } } memcpy(current, next, sizeof(current)); generation++; return board_changed; } // ==================== Render current grid to display ==================== void render_grid() { hub75_clear(); // Clear the draw buffer for (int cy = 0; cy < CELL_H; cy++) { for (int cx = 0; cx < CELL_W; cx++) { uint8_t age = current[cx][cy]; if (age > 0) { rgb_t color; // Color Explosion Logic! if (age == 1) { // Newborn cells flash stark white for maximum contrast color = RGB_WHITE; } else { // Older cells shift through the rainbow based on BOTH age and generation. uint16_t hue = (generation * 4 + age * 8) % 360; color = hsv_to_rgb(hue, 255, 255); } // Draw a single pixel for 1:1 native resolution mapping hub75_set_pixel(cx, cy, color); } } } } // ==================== Setup (Core 0) ==================== void setup() { Serial.begin(115200); delay(1000); hub75_init(); hub75_set_brightness(200); // Initialize the random number generator randomSeed(micros()); // Kick off the first generation using the Jukebox! run_jukebox_spawner(); Serial.println("Conway's Game of Life (Endless Jukebox Edition) started"); engine_ready = true; } // ==================== Main loop (Core 0) ==================== // ==================== Main loop (Core 0) ==================== void loop() { static unsigned long last_frame = 0; // Limit frame rate to ~15 fps (66 ms per gen) unsigned long now = millis(); if (now - last_frame < 66) { delay(1); return; } last_frame = now; // 1. Compute the next generation bool changed = compute_next_generation(); if (changed) { last_changed_gen = generation; } // --- NEW: THE COSMIC RAY DISRUPTOR --- // Every 40 frames, if the board is still alive, drop 1 random pixel on it. // If the board is stuck in an endless oscillator loop, this will blow it up! if (generation % 40 == 0 && current_population > 0) { safe_spawn(random(0, CELL_W), random(0, CELL_H)); } // --- NEW: THE SMART AUTO-RESET ENGINE --- // Trigger 1: INSTANT RESET. If the board is totally empty, don't wait. if (current_population == 0) { run_jukebox_spawner(); } // Trigger 2: TRUE STAGNATION. If absolutely nothing has moved in 20 frames. else if (generation - last_changed_gen > 50) { run_jukebox_spawner(); } // Trigger 3: THE HARD LIMIT. Cut down to 800 frames so we cycle through // the 50 patterns much faster and keep things visually exciting. else if (generation > 800) { run_jukebox_spawner(); } // 3. Render the new generation into the draw buffer render_grid(); // 4. Swap buffers – the display now shows the new generation hub75_swap_buffers(); } // ==================== CORE 1 (Screen Refresh) ==================== void setup1() {} void loop1() { if (!engine_ready) return; // Run the HUB75 driver's refresh system purely on Core 1 // This ensures your LED matrix never flickers while Core 0 does the heavy math! hub75_refresh(); }