/ Pi_Matrix_Cube / life.cc
life.cc
  1  // SPDX-FileCopyrightText: 2022 Phillip Burgess for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  /*
  6  Conway's Game of Life for 6X square RGB LED matrices.
  7  Uses same physical matrix arrangement as "globe" program; see notes there.
  8  
  9  usage: sudo ./life [options]
 10  
 11  (You may or may not need the 'sudo' depending how the rpi-rgb-matrix
 12  library is configured)
 13  
 14  Options include all of the rpi-rgb-matrix flags, such as --led-pwm-bits=N
 15  or --led-gpio-slowdown=N, and then the following:
 16  
 17    -k <int>   : Index of color palette to use. 0 = default black & white.
 18                 (Sorry, -c and -p are both rpi-rgb-matrix abbreviations.
 19                  Consider this a Monty Python Travel Agent Sketch nod.)
 20    -t <float> : Run time in seconds. Program will exit after this.
 21                 Default is to run indefinitely, until crtl+C received.
 22    -f <float> : Fade in/out time in seconds. Used in combination with the
 23                 -t option, this provides a nice fade-in, run for a while,
 24                 fade-out and exit.
 25  
 26  rpi-rgb-matrix has the following single-character abbreviations for
 27  some configurables: -b (--led-brightness), -c (--led-chain),
 28  -m (--led-gpio-mapping), -p (--led-pwm-bits), -P (--led-parallel),
 29  -r (--led-rows). AVOID THESE in any future configurables added to this
 30  program, as some users may have "muscle memory" for those options.
 31  
 32  This code depends on the rpi-rgb-matrix library. While this .cc file has
 33  a permissive MIT licence, libraries may (or not) have restrictions on
 34  commercial use, distributing precompiled binaries, etc.  Check their
 35  license terms if this is relevant to your situation.
 36  */
 37  
 38  #include <getopt.h>
 39  #include <led-matrix.h>
 40  #include <math.h>
 41  #include <signal.h>
 42  #include <stdio.h>
 43  #include <string.h>
 44  #include <sys/time.h>
 45  
 46  using namespace rgb_matrix;
 47  
 48  // GLOBAL VARIABLES --------------------------------------------------------
 49  
 50  typedef enum {
 51    EDGE_TOP = 0,
 52    EDGE_LEFT,
 53    EDGE_RIGHT,
 54    EDGE_BOTTOM,
 55  } Edge;
 56  
 57  // Colormaps appear reversed from what one might expect. The first element
 58  // of each is the 'on' pixel color, and each subsequent element is the color
 59  // as a pixel 'ages,' up to the final 'background' color. Hence simple B&W
 60  // on/off palette is white in index 0, black in index 1.
 61  
 62  uint8_t map_bw[][3] = { // Simple B&W
 63      {255, 255, 255},
 64      {0, 0, 0}};
 65  
 66  uint8_t map_gray[][3] = { // Log2 grayscale
 67      {255, 255, 255},      // clang-format,
 68      {127, 127, 127},      // I love you
 69      {63, 63, 63},         // but
 70      {31, 31, 31},         // why
 71      {15, 15, 15},         // you
 72      {7, 7, 7},            // gotta
 73      {3, 3, 3},            // do
 74      {1, 1, 1},            // this
 75      {0, 0, 0}};           // to me?
 76  
 77  uint8_t map_heat[][3] = {
 78      // Heatmap (white-yellow-red-black)
 79      {255, 255, 255}, // White
 80      {255, 255, 127}, // Two steps to...
 81      {255, 255, 0},   // Yellow
 82      {255, 170, 0},   // Three steps...
 83      {255, 85, 0},    // to...
 84      {255, 0, 0},     // Red
 85      {204, 0, 0},     // Four steps...
 86      {153, 0, 0},     //
 87      {102, 0, 0},     // to...
 88      {51, 0, 0},      //
 89      {0, 0, 0}        // Black
 90  };
 91  
 92  uint8_t map_spec[][3] = {
 93      // Color spectrum
 94      {255, 255, 255}, // White (100%)
 95      {127, 0, 0},     // Red (50%)
 96      {127, 31, 0},    // to...
 97      {127, 63, 0},    // Orange (50%)
 98      {127, 95, 0},    // to...
 99      {127, 127, 0},   // Yellow (etc)
100      {63, 127, 0},    // to...
101      {0, 127, 0},     // Green
102      {0, 127, 127},   // Cyan
103      {0, 0, 127},     // Blue
104      {63, 0, 127},    // to...
105      {127, 0, 127},   // Magenta
106      {82, 0, 82},     // fade...
107      {41, 0, 41},     // to...
108      {0, 0, 0}        // Black
109  };
110  
111  struct {
112    uint8_t *data;
113    uint8_t max;
114  } colormaps[] = {
115      {(uint8_t *)map_bw, sizeof(map_bw) / sizeof(map_bw[0]) - 1},
116      {(uint8_t *)map_gray, sizeof(map_gray) / sizeof(map_gray[0]) - 1},
117      {(uint8_t *)map_heat, sizeof(map_heat) / sizeof(map_heat[0]) - 1},
118      {(uint8_t *)map_spec, sizeof(map_spec) / sizeof(map_spec[0]) - 1},
119  };
120  
121  #define NUM_PALETTES (sizeof(colormaps) / sizeof(colormaps[0]))
122  
123  struct {
124    uint8_t face;  // Index of face off this edge
125    Edge edge;     // Which edge of face its entering that way
126  } face[6][4] = { // Order is top, left, right, bottom
127    {{1, EDGE_LEFT}, {2, EDGE_TOP}, {4, EDGE_TOP}, {3, EDGE_RIGHT}},
128    {{2, EDGE_LEFT}, {0, EDGE_TOP}, {5, EDGE_TOP}, {4, EDGE_RIGHT}},
129    {{0, EDGE_LEFT}, {1, EDGE_TOP}, {3, EDGE_TOP}, {5, EDGE_RIGHT}},
130    {{2, EDGE_RIGHT}, {5, EDGE_BOTTOM}, {0, EDGE_BOTTOM}, {4, EDGE_LEFT}},
131    {{0, EDGE_RIGHT}, {3, EDGE_BOTTOM}, {1, EDGE_BOTTOM}, {5, EDGE_LEFT}},
132    {{1, EDGE_RIGHT}, {4, EDGE_BOTTOM}, {2, EDGE_BOTTOM}, {3, EDGE_LEFT}}};
133  
134  
135  // These globals have defaults but are runtime configurable:
136  uint16_t matrix_size = 64; // Matrix X&Y pixel count (must be square)
137  uint16_t matrix_max = matrix_size - 1; // Matrix X&Y max coord
138  float run_time = -1.0;        // Time before exit (negative = run forever)
139  float fade_time = 0.0;        // Fade in/out time (if run_time is set)
140  float max_brightness = 255.0; // Fade up to, down from this value
141  
142  // These globals are computed or allocated at runtime after taking input:
143  uint8_t *data[2]; // Cell arrays; current and next-in-progress
144  uint8_t idx = 0;  // Which data[] array is current
145  uint8_t *colormap;
146  uint8_t colormap_max;
147  
148  // INTERRUPT HANDLER (to allow clearing matrix on exit) --------------------
149  
150  volatile bool interrupt_received = false;
151  static void InterruptHandler(int signo) { interrupt_received = true; }
152  
153  // COMMAND-LINE HELP -------------------------------------------------------
154  
155  static int usage(const char *progname) {
156    fprintf(stderr, "usage: %s [options]\n", progname);
157    fprintf(stderr, "Options:\n");
158    rgb_matrix::PrintMatrixFlags(stderr);
159    fprintf(stderr, "\t-k <int>   : Color palette (0-%d)\n", NUM_PALETTES - 1);
160    fprintf(stderr, "\t-t <float> : Run time in seconds\n");
161    fprintf(stderr, "\t-f <float> : Fade in/out time in seconds\n");
162    return 1;
163  }
164  
165  // SUNDRY UTILITY-LIKE FUNCTIONS -------------------------------------------
166  
167  uint8_t cross(uint8_t f, Edge e, int16_t *x, int16_t *y) {
168    switch ((e << 2) | face[f][e].edge) {
169    case (EDGE_TOP << 2) | EDGE_TOP:
170      *x = matrix_max - *x;
171      *y = 0;
172      break;
173    case (EDGE_TOP << 2) | EDGE_LEFT:
174      *y = *x;
175      *x = 0;
176      break;
177    case (EDGE_TOP << 2) | EDGE_RIGHT:
178      *y = matrix_max - *x;
179      *x = matrix_max;
180      break;
181    case (EDGE_TOP << 2) | EDGE_BOTTOM:
182      *y = matrix_max;
183      break;
184    case (EDGE_LEFT << 2) | EDGE_TOP:
185      *x = *y;
186      *y = 0;
187      break;
188    case (EDGE_LEFT << 2) | EDGE_LEFT:
189      *x = 0;
190      *y = matrix_max - *y;
191      break;
192    case (EDGE_LEFT << 2) | EDGE_RIGHT:
193      *x = matrix_max;
194      break;
195    case (EDGE_LEFT << 2) | EDGE_BOTTOM:
196      *x = matrix_max - *y;
197      *y = matrix_max;
198      break;
199    case (EDGE_RIGHT << 2) | EDGE_TOP:
200      *x = matrix_max - *y;
201      *y = 0;
202      break;
203    case (EDGE_RIGHT << 2) | EDGE_LEFT:
204      *x = 0;
205      break;
206    case (EDGE_RIGHT << 2) | EDGE_RIGHT:
207      *x = matrix_max;
208      *y = matrix_max - *y;
209      break;
210    case (EDGE_RIGHT << 2) | EDGE_BOTTOM:
211      *x = *y;
212      *y = matrix_max;
213      break;
214    case (EDGE_BOTTOM << 2) | EDGE_TOP:
215      *y = 0;
216      break;
217    case (EDGE_BOTTOM << 2) | EDGE_LEFT:
218      *y = matrix_max - *x;
219      *x = 0;
220      break;
221    case (EDGE_BOTTOM << 2) | EDGE_RIGHT:
222      *y = *x;
223      *x = matrix_max;
224      break;
225    case (EDGE_BOTTOM << 2) | EDGE_BOTTOM:
226      *x = matrix_max - *x;
227      *y = matrix_max;
228      break;
229    }
230  
231    return face[f][e].face;
232  }
233  
234  uint8_t getPixel(uint8_t f, int16_t x, int16_t y) {
235    if (x >= 0) {
236      if (x < matrix_size) {
237        // Pixel is within X range
238        if (y >= 0) {
239          if (y < matrix_size) {
240            // Pixel is within face bounds
241            return data[idx][(f * matrix_size + y) * matrix_size + x];
242          } else {
243            // Pixel is off bottom edge (but within X bounds)
244            f = cross(f, EDGE_BOTTOM, &x, &y);
245            return data[idx][(f * matrix_size + y) * matrix_size + x];
246          }
247        } else {
248          // Pixel is off top edge (but within X bounds)
249          f = cross(f, EDGE_TOP, &x, &y);
250          return data[idx][(f * matrix_size + y) * matrix_size + x];
251        }
252      } else {
253        // Pixel is off right edge
254        if ((y >= 0) && (y < matrix_size)) {
255          // Pixel is off right edge (but within Y bounds)
256          f = cross(f, EDGE_RIGHT, &x, &y);
257          return data[idx][(f * matrix_size + y) * matrix_size + x];
258        }
259      }
260    } else {
261      // Pixel is off left edge
262      if ((y >= 0) && (y < matrix_size)) {
263        // Pixel is off left edge (but within Y bounds)
264        f = cross(f, EDGE_LEFT, &x, &y);
265        return data[idx][(f * matrix_size + y) * matrix_size + x];
266      }
267    }
268  
269    // Pixel is off both X&Y edges. Because of cube topology,
270    // there isn't really a pixel there.
271    return 1; // 1 = dead pixel
272  }
273  
274  void setPixel(uint8_t f, int16_t x, int16_t y, uint8_t value) {
275    data[1 - idx][(f * matrix_size + y) * matrix_size + x] = value;
276  }
277  
278  // MAIN CODE ---------------------------------------------------------------
279  
280  int main(int argc, char *argv[]) {
281    RGBMatrix *matrix;
282    FrameCanvas *canvas;
283  
284    // INITIALIZE DEFAULTS and PROCESS COMMAND-LINE INPUT --------------------
285  
286    RGBMatrix::Options matrix_options;
287    rgb_matrix::RuntimeOptions runtime_opt;
288  
289    matrix_options.cols = matrix_options.rows = matrix_size;
290    matrix_options.chain_length = 6;
291    runtime_opt.gpio_slowdown = 4; // For Pi 4 w/6 matrices
292  
293    // Parse matrix-related command line options first
294    if (!ParseOptionsFromFlags(&argc, &argv, &matrix_options, &runtime_opt)) {
295      return usage(argv[0]);
296    }
297  
298    // Validate inputs for cube-like behavior
299    if (matrix_options.cols != matrix_options.rows) {
300      fprintf(stderr, "%s: matrix columns, rows must be equal (square matrix)\n",
301              argv[0]);
302      return 1;
303    }
304    if (matrix_options.chain_length * matrix_options.parallel != 6) {
305      fprintf(stderr, "%s: total chained/parallel matrices must equal 6\n",
306              argv[0]);
307      return 1;
308    }
309  
310    max_brightness = (float)matrix_options.brightness * 2.55; // 0-100 -> 0-255
311  
312    // Then parse any lingering program options (filename, etc.)
313    int opt;
314    uint8_t palettenum = 0;
315    while ((opt = getopt(argc, argv, "k:t:f:")) != -1) {
316      switch (opt) {
317      case 'k':
318        palettenum = atoi(optarg);
319        if (palettenum >= NUM_PALETTES)
320          palettenum = NUM_PALETTES - 1;
321        else if (palettenum < 0)
322          palettenum = 0;
323        break;
324      case 't':
325        run_time = fabs(strtof(optarg, NULL));
326        break;
327      case 'f':
328        fade_time = fabs(strtof(optarg, NULL));
329        break;
330      default: // '?'
331        return usage(argv[0]);
332      }
333    }
334  
335    // LOAD and ALLOCATE DATA STRUCTURES -------------------------------------
336  
337    // Allocate cell arrays
338    matrix_size = matrix_options.rows;
339    matrix_max = matrix_size - 1;
340    int num_elements = 6 * matrix_size * matrix_size;
341    if (!(data[0] = (uint8_t *)malloc(num_elements * 2 * sizeof(uint8_t)))) {
342      fprintf(stderr, "%s: can't allocate space for cell data\n", argv[0]);
343      return 1;
344    }
345    data[1] = &data[0][num_elements];
346  
347    colormap = colormaps[palettenum].data;
348    colormap_max = colormaps[palettenum].max;
349  
350    // Randomize initial state; 50% chance of any pixel being set
351    int i;
352    for (i = 0; i < num_elements; i++) {
353      data[idx][i] = (rand() & 1) * colormap_max;
354    }
355  
356    // INITIALIZE RGB MATRIX CHAIN and OFFSCREEN CANVAS ----------------------
357  
358    if (!(matrix = RGBMatrix::CreateFromOptions(matrix_options, runtime_opt))) {
359      fprintf(stderr, "%s: couldn't create matrix object\n", argv[0]);
360      return 1;
361    }
362    if (!(canvas = matrix->CreateFrameCanvas())) {
363      fprintf(stderr, "%s: couldn't create canvas object\n", argv[0]);
364      return 1;
365    }
366  
367    // OTHER MINOR INITIALIZATION --------------------------------------------
368  
369    signal(SIGTERM, InterruptHandler);
370    signal(SIGINT, InterruptHandler);
371  
372    struct timeval startTime, now;
373    gettimeofday(&startTime, NULL); // Program start time
374  
375    // LOOP RUNS INDEFINITELY OR UNTIL CTRL+C or run_time ELAPSED ------------
376  
377    uint32_t frames = 0;
378    int prevsec = -1;
379  
380    while (!interrupt_received) {
381      gettimeofday(&now, NULL);
382      double elapsed = ((now.tv_sec - startTime.tv_sec) +
383                        (now.tv_usec - startTime.tv_usec) / 1000000.0);
384      if (run_time > 0.0) { // Handle time limit and fade in/out if needed...
385        if (elapsed >= run_time)
386          break;
387        if (elapsed < fade_time) {
388          matrix->SetBrightness((int)(max_brightness * elapsed / fade_time));
389        } else if (elapsed > (run_time - fade_time)) {
390          matrix->SetBrightness(
391              (int)(max_brightness * (run_time - elapsed) / fade_time));
392        } else {
393          matrix->SetBrightness(max_brightness);
394        }
395      }
396      // Do life stuff...
397      int newidx = 1 - idx;
398      for (uint8_t f = 0; f < 6; f++) {
399        // Upper-left corner of face in canvas space:
400        int xoffset = (f % matrix_options.chain_length) * matrix_size;
401        int yoffset = (f / matrix_options.chain_length) * matrix_size;
402        for (int y = 0; y < matrix_size; y++) {
403          for (int x = 0; x < matrix_size; x++) {
404            // Value returned by getPixel is the "age" of that pixel being
405            // empty...thus, 0 actually means pixel is currently occupied,
406            // hence all the ! here when counting neighbors...
407            uint8_t neighbors =
408                !getPixel(f, x - 1, y - 1) + !getPixel(f, x, y - 1) +
409                !getPixel(f, x + 1, y - 1) + !getPixel(f, x - 1, y) +
410                !getPixel(f, x + 1, y) + !getPixel(f, x - 1, y + 1) +
411                !getPixel(f, x, y + 1) + !getPixel(f, x + 1, y + 1);
412            // Live cell w/2 or 3 neighbors continues, else dies.
413            // Empty cell w/3 neighbors goes live.
414            uint8_t n = getPixel(f, x, y);
415            if (n == 0) { // Pixel (x,y) is 'alive'
416              if ((neighbors < 2) || (neighbors > 3))
417                n = 1; // Pixel 'dies'
418            } else {   // Pixel (x,y) is 'dead'
419              if (neighbors == 3)
420                n = 0; // Wake up!
421              else if (n < colormap_max)
422                n += 1; // Decay
423            }
424            setPixel(f, x, y, n);
425            n *= 3; // Convert color index to RGB offset
426            canvas->SetPixel(xoffset + x, yoffset + y, colormap[n],
427                             colormap[n + 1], colormap[n + 2]);
428          }
429        }
430      }
431      canvas = matrix->SwapOnVSync(canvas);
432      idx = newidx;
433      frames++;
434      if (now.tv_sec != prevsec) {
435        if (prevsec >= 0) {
436          printf("%f\n", frames / elapsed);
437        }
438        prevsec = now.tv_sec;
439      }
440    }
441  
442    canvas->Clear();
443    delete matrix;
444  
445    return 0;
446  }