pbnj

snippet #18

#include <Arduino.h>
#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();
}