2

I have a problem when changing MAX_X from 8 to 24. I know that the problem is that I am using to many global variables but I don't know how to fix it.

#include <LedControl.h>

#include "LedControl.h"

// "DIN" data in pin #define DIN_PIN 12 // "CLK" clock pin #define CLK_PIN 11 // "CS" pin #define CS_PIN 10 // grid dimensions. should not be larger than 8x8 #define MAX_Y 8 #define MAX_X 24 // how many custom starting grid patterns #define MAX_C 15 // time to wait between turns #define TURN_DELAY 100 // how many turns per game before starting a new game // you can also use the reset button on the board #define TURNS_MAX 40 // how many turns to wait if there are no changes before starting a new game #define NO_CHANGES_RESET 4

int TURNS = 0; // counter for turns int NO_CHANGES = 0; // counter for turns without changes

// game state. 0 is dead cell, 1 is live cell boolean grid[MAX_Y][MAX_X] = { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, };

// custom starting grid patterns boolean cgrids[MAX_C][MAX_Y][MAX_X] = { { {1, 1, 0, 0, 1, 1, 0, 0}, {1, 1, 0, 0, 1, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 1, 1}, {0, 0, 0, 0, 0, 0, 1, 1}, {1, 1, 0, 0, 0, 0, 0, 0}, {1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 0, 0, 1, 1}, {0, 0, 1, 1, 0, 0, 1, 1}, }, { {1, 0, 1, 0, 0, 0, 1, 0}, {0, 1, 1, 0, 0, 0, 1, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 1, 0, 0, 0, 1, 1, 0}, {0, 1, 0, 0, 0, 1, 0, 1}, }, { {1, 0, 1, 0, 0, 0, 0, 0}, {0, 1, 1, 0, 0, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 0, 0, 0, 0, 0}, {1, 0, 1, 0, 0, 0, 0, 0}, }, { {1, 0, 1, 0, 0, 1, 0, 1}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, }, { {1, 0, 1, 0, 0, 0, 0, 0}, {0, 1, 1, 0, 0, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, }, { {0, 1, 0, 0, 0, 0, 0, 0}, {0, 1, 0, 0, 0, 1, 1, 1}, {0, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 1, 0}, {1, 1, 1, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 0, 1, 0}, }, { // https://conwaylife.com/wiki/Mold {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 1, 0, 0}, {0, 0, 0, 1, 0, 0, 1, 0}, {0, 0, 1, 0, 1, 0, 1, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 0, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, }, { // https://conwaylife.com/wiki/Octagon_2 {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, }, { {1, 0, 1, 0, 0, 1, 0, 1}, {1, 1, 0, 0, 0, 0, 1, 0}, {1, 0, 0, 1, 1, 1, 0, 1}, {0, 0, 1, 1, 0, 1, 0, 0}, {1, 0, 0, 0, 1, 1, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 1, 0}, {1, 0, 1, 1, 0, 1, 1, 1}, }, { {1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 0, 0, 0, 0, 1, 1}, {1, 0, 0, 1, 1, 1, 0, 1}, {1, 0, 1, 1, 0, 1, 0, 1}, {1, 0, 1, 0, 1, 1, 0, 1}, {1, 0, 1, 1, 1, 0, 0, 1}, {1, 1, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1}, }, { {1, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 1, 1}, {1, 0, 0, 1, 1, 1, 0, 1}, {1, 0, 1, 1, 0, 1, 0, 1}, {1, 0, 1, 0, 1, 1, 0, 1}, {1, 0, 1, 1, 1, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1}, }, { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 0, 0, 0, 0, 0}, {0, 0, 1, 0, 0, 0, 0, 0}, {0, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {1, 1, 0, 0, 1, 1, 1, 0}, {1, 0, 1, 0, 1, 0, 0, 0}, {0, 1, 1, 0, 0, 0, 0, 0}, }, { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 0, 0, 0, 0}, {0, 0, 1, 1, 0, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 0, 0, 0, 0}, {0, 0, 0, 1, 0, 0, 0, 0}, }, { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, }, { {1, 0, 0, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 1, 0, 1}, {0, 0, 0, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 1, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 0, 1}, }, };

LedControl lc = LedControl(DIN_PIN, CS_PIN, CLK_PIN, 1);

void setup() { // seed random from unused analog pin randomSeed(analogRead(0));

// initialise the LED matrix lc.shutdown(0, false); lc.setIntensity(0, 0); lc.clearDisplay(0);

reset_grid(); display_grid();

// Serial.begin(9600); // debug_grid(); }

void loop() { delay(TURN_DELAY);

play_gol();

TURNS++;

// reset the grid if no changes have occured recently // for when the game enters a static stable state if (NO_CHANGES > NO_CHANGES_RESET) { reset_grid(); } // reset the grid if the loop has been running a long time // for when the game cycles between a few stable states if (TURNS > TURNS_MAX) { reset_grid(); }

display_grid(); }

// play game of life void play_gol() { /* 1. Any live cell with fewer than two neighbours dies, as if by loneliness. 2. Any live cell with more than three neighbours dies, as if by overcrowding. 3. Any live cell with two or three neighbours lives, unchanged, to the next generation. 4. Any dead cell with exactly three neighbours comes to life. */

boolean new_grid[MAX_Y][MAX_X] = { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, };

for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { int neighboughs = count_neighboughs(y, x); if (grid[y][x] == 1) { if ((neighboughs == 2) || (neighboughs == 3)) { new_grid[y][x] = 1; } else { new_grid[y][x] = 0; } } else { if (neighboughs == 3) { new_grid[y][x] = 1; } else { new_grid[y][x] = 0; } } } }

// update the current grid from the new grid and count how many changes // occured int changes = 0; for (int y = 0; y < MAX_Y; y++) { for (int x = 0; x < MAX_X; x++) { if (new_grid[y][x] != grid[y][x]) { changes++; } grid[y][x] = new_grid[y][x]; } }

// update global counter when no changes occured if (changes == 0) { NO_CHANGES++; } }

// count the number of neighbough live cells for a given cell int count_neighboughs(int y, int x) { int count = 0;

// -- Row above us --- if (y > 0) { // above left if (x > 0) { count += grid[y - 1][x - 1]; } // above count += grid[y - 1][x]; // above right if ((x + 1) < 8) { count += grid[y - 1][x + 1]; } }

// -- Same row ------- // left if (x > 0) { count += grid[y][x - 1]; } // right if ((x + 1) < 8) { count += grid[y][x + 1]; }

// -- Row below us --- if ((y + 1) < 8) { // below left if (x > 0) { count += grid[y + 1][x - 1]; } // below count += grid[y + 1][x]; // below right if ((x + 1) < 8) { count += grid[y + 1][x + 1]; } }

return count; }

// reset the grid void reset_grid() { NO_CHANGES = 0; TURNS = 0;

int grid_type = random(0, 4); int custom_grid_choice = random(0, MAX_C - 1);

for (int y = 0; y < MAX_Y; y++) { for (int x = 0; x < MAX_X; x++) { if (grid_type == 0) { // use a custom starting grid pattern grid[y][x] = cgrids[custom_grid_choice][y][x]; } else { // create a random starting grid pattern if (random(0, MAX_X) <= 1) { grid[y][x] = 1; } } } } }

// display the current grid to the LED matrix void display_grid() { for (int y = 0; y < MAX_Y; y++) { for (int x = 0; x < MAX_X; x++) { lc.setLed(0, y, x, grid[y][x]); } } }

/* // dump the state of the current grid to the serial connection void debug_grid() { for (int y = 0; y < MAX_Y; y++) { Serial.print("y("); Serial.print(y); Serial.print("): ");

for (int x = 0; x &lt; MAX_X; x++) {
  Serial.print(grid[y][x]);
  Serial.print(&quot;, &quot;);
}

Serial.println(&quot;&quot;);

} Serial.println(""); } */

Greenonline
  • 2,938
  • 7
  • 32
  • 48
Augustus73
  • 21
  • 1

2 Answers2

4

The obvious “low hanging fruit” for memory optimization is cgrids. This is an array of 8×24×15 = 2880 booleans that you never modify. By making it const and storing it in PROGMEM, you should save that many bytes of RAM.

Edit: Augustus73 asked

And how should I do that?

As explained in the documentation I linked to, using PROGMEM is a two-step procedure. First, you have to ask the compiler to put the array in PROGMEM:

PROGMEM const boolean cgrids[MAX_C][MAX_Y][MAX_X] = { ... };

Then, you have to use one of the pgm_read_*() macros when you want to retrieve the data, as in:

grid[y][x] = pgm_read_byte(&cgrids[custom_grid_choice][y][x]);
Edgar Bonet
  • 43,033
  • 4
  • 38
  • 76
0

Your use of a byte per bit in your array is inefficient.

I have reworked the code to use a bit for each array item, as follows:

#include <LedControl.h>

// "DIN" data in pin #define DIN_PIN 12 // "CLK" clock pin #define CLK_PIN 11 // "CS" pin #define CS_PIN 10 // grid dimensions. should not be larger than 8x8 #define MAX_Y 8 #define MAX_X 24 // how many custom starting grid patterns #define MAX_C 15 // time to wait between turns #define TURN_DELAY 1000 // how many turns per game before starting a new game // you can also use the reset button on the board #define TURNS_MAX 40 // how many turns to wait if there are no changes before starting a new game #define NO_CHANGES_RESET 4

int TURNS = 0; // counter for turns int NO_CHANGES = 0; // counter for turns without changes

// game state. 0 is dead cell, 1 is live cell

unsigned long grid[MAX_Y]; // Each unsigned long can hold 32 bits

// Added by Nick Gammon void setBit (unsigned long *whichGrid, const unsigned char y, const unsigned char x, const bool value) { if (value) whichGrid[y] |= (1U << x); else whichGrid[y] &= ~(1U << x); } // end of setBit

bool getBit (unsigned long *whichGrid, const unsigned char y, const unsigned char x) { return whichGrid[y] & (1U << x); } // end of testBit

// custom starting grid patterns unsigned long cgrids[MAX_C][MAX_Y] = { { 0b11001100, 0b11001100, 0b00000011, 0b00000011, 0b11000000, 0b11000000, 0b00110011, 0b00110011, }, { 0b10100010, 0b01100010, 0b01000010, 0b00000000, 0b00000000, 0b01000010, 0b01000110, 0b01000101, },

{ 0b10100000, 0b01100000, 0b01000000, 0b00000000, 0b00000000, 0b01000000, 0b01100000, 0b10100000, }, { 0b10100101, 0b01100110, 0b01000010, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, }, { 0b10100000, 0b01100000, 0b01000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, }, { 0b01000000, 0b01000111, 0b01000000, 0b00000000, 0b00000000, 0b00000010, 0b11100010, 0b00000010, }, { // https://conwaylife.com/wiki/Mold 0b00000000, 0b00001100, 0b00010010, 0b00101010, 0b00100100, 0b01000000, 0b00101000, 0b00000000, }, { // https://conwaylife.com/wiki/Octagon_2 0b00011000, 0b00100100, 0b01000010, 0b10000001, 0b10000001, 0b01000010, 0b00100100, 0b00011000, }, { 0b10100101, 0b11000010, 0b10011101, 0b00110100, 0b10001100, 0b10001001, 0b00000010, 0b10110111, }, { 0b11111111, 0b11000011, 0b10011101, 0b10110101, 0b10101101, 0b10111001, 0b11000001, 0b11111111, }, { 0b11111111, 0b10000011, 0b10011101, 0b10110101, 0b10101101, 0b10111001, 0b10000001, 0b11111111, }, { 0b00000000, 0b00100000, 0b00100000, 0b01100000, 0b00000000, 0b11001110, 0b10101000, 0b01100000, }, { 0b00000000, 0b00000000, 0b00000000, 0b00010000, 0b00110000, 0b01000000, 0b00110000, 0b00010000, }, { 0b00000000, 0b00000000, 0b01100110, 0b01100110, 0b01100110, 0b01100110, 0b00000000, 0b00000000, }, { 0b10000100, 0b01000010, 0b00100101, 0b00010000, 0b00001000, 0b10100100, 0b01000010, 0b00100001, },

};

LedControl lc = LedControl(DIN_PIN, CS_PIN, CLK_PIN, 1);

void setup() { // seed random from unused analog pin randomSeed(analogRead(0));

// initialise the LED matrix lc.shutdown(0, false); lc.setIntensity(0, 0); lc.clearDisplay(0);

reset_grid(); display_grid();

Serial.begin(115200); debug_grid(); }

void loop() { delay(TURN_DELAY);

play_gol();

TURNS++;

// reset the grid if no changes have occured recently // for when the game enters a static stable state if (NO_CHANGES > NO_CHANGES_RESET) { reset_grid(); } // reset the grid if the loop has been running a long time // for when the game cycles between a few stable states if (TURNS > TURNS_MAX) { reset_grid(); }

display_grid(); }

// play game of life void play_gol() { /* 1. Any live cell with fewer than two neighbours dies, as if by loneliness. 2. Any live cell with more than three neighbours dies, as if by overcrowding. 3. Any live cell with two or three neighbours lives, unchanged, to the next generation. 4. Any dead cell with exactly three neighbours comes to life. */

unsigned long new_grid[MAX_Y]; // Each unsigned long can hold 32 bits

for (int y = 0; y < MAX_Y; y++) { for (int x = 0; x < MAX_X; x++) { int neighboughs = count_neighboughs(y, x); if (getBit (grid, y, x)) { if ((neighboughs == 2) || (neighboughs == 3)) { setBit (new_grid, y, x, 1); } else { setBit (new_grid, y, x, 0); } } else { if (neighboughs == 3) { setBit (new_grid, y, x, 1); } else { setBit (new_grid, y, x, 0); } } } }

// update the current grid from the new grid and count how many changes // occured int changes = 0; for (int y = 0; y < MAX_Y; y++) { for (int x = 0; x < MAX_X; x++) { if (getBit (new_grid, y, x) != getBit (grid, y, x)) { changes++; } setBit (grid, y, x, getBit (new_grid, y, x)); } }

// update global counter when no changes occured if (changes == 0) { NO_CHANGES++; } }

// count the number of neighbough live cells for a given cell int count_neighboughs(int y, int x) { int count = 0;

// -- Row above us --- if (y > 0) { // above left if (x > 0) { count += getBit (grid, y - 1, x - 1); } // above count += getBit (grid, y - 1, x); // above right if ((x + 1) < MAX_X) { count += getBit (grid, y - 1, x + 1); } }

// -- Same row ------- // left if (x > 0) { count += getBit (grid, y, x - 1); } // right if ((x + 1) < MAX_X) { count += getBit (grid, y, x + 1); }

// -- Row below us --- if ((y + 1) < MAX_Y) { // below left if (x > 0) { count += getBit (grid, y + 1, x - 1); } // below count += getBit (grid, y + 1, x); // below right if ((x + 1) < MAX_X) { count += getBit (grid, y + 1, x + 1); } }

return count; }

// reset the grid void reset_grid() { NO_CHANGES = 0; TURNS = 0;

Serial.println("Resetting the grid");

int grid_type = random(0, 4); int custom_grid_choice = random(0, MAX_C - 1);

for (int y = 0; y < MAX_Y; y++) { for (int x = 0; x < MAX_X; x++) { if (grid_type == 0) { // use a custom starting grid pattern setBit (grid, y, x, getBit (cgrids[custom_grid_choice], y, x)); } else { // create a random starting grid pattern if (random(0, MAX_X) <= 1) { setBit (grid, y, x, 1); } } } } }

// display the current grid to the LED matrix void display_grid() { for (int y = 0; y < MAX_Y; y++) { for (int x = 0; x < MAX_X; x++) { lc.setLed(0, y, x, getBit (grid, y, x)); } } debug_grid (); }

// dump the state of the current grid to the serial connection void debug_grid() { Serial.print (" "); for (int x = 0; x < MAX_X; x++) Serial.print ("."); Serial.println ();

for (int y = 0; y < MAX_Y; y++) { Serial.print("y("); Serial.print(y); Serial.print("): ");

for (int x = 0; x &lt; MAX_X; x++) {
  if (getBit (grid, y, x))
    Serial.print (&quot;*&quot;);
  else
    Serial.print (&quot; &quot;);
}

Serial.println(&quot;&quot;);

} Serial.println(""); }

Note, in particular, the functions setBit and getBit which are designed to test, set or clear a particular bit, as indexed in your earlier implementation of arrays of one byte.

I then reworked the code to use these new functions, and changed your declaration of the cgrids to match. Testing on the serial terminal seems to work. Your code didn't do anything useful for me on my LED display.

// Added by Nick Gammon
void setBit (unsigned long *whichGrid, const unsigned char y, const unsigned char x, const bool value)
  {
  if (value)
    whichGrid[y] |= (1U << x);
  else
    whichGrid[y] &= ~(1U << x);
  } // end of setBit

bool getBit (unsigned long *whichGrid, const unsigned char y, const unsigned char x) { return whichGrid[y] & (1U << x); } // end of testBit

I also fixed a few bugs in your code where you were testing for X or Y reaching 8 rather than MAX_X or MAX_Y which would have worked when MAX_X and MAX_Y were 8, but not if you make them bigger.

This version uses a lot less memory:

Sketch uses 4876 bytes (15%) of program storage space. Maximum is 32256 bytes.

I reworked your debugging display a bit since the LEDs didn't work for me. Example output:

      ........................
y(0):           ****          
y(1):   **        * *         
y(2):   **     ** * **        
y(3):          *  * *         
y(4):             *           
y(5):                         
y(6):                         
y(7):
  ........................

y(0): ***
y(1): ** * **
y(2): ** ** * **
y(3): ** * **
y(4): *
y(5):
y(6):
y(7):

  ........................

y(0): ***
y(1): ** * *
y(2): ** *
y(3): ** * *
y(4): **
y(5):
y(6):
y(7):

  ........................

y(0): **
y(1): ** **
y(2): ** * *
y(3): * **
y(4): **
y(5):
y(6):
y(7):


Just for interest, I made a Game of Life in 2011 for the Lots Of LEDs shield. It's a fun project.

Edit - to be fair, I think the code was actually written by the author of the shield: "Created by Jimmie Rodgers on 12/30/2009".

However I do recall writing a Game of Life for the Apple II, many years ago. The source code for that is long gone. :)

Game of Life

Nick Gammon
  • 38,184
  • 13
  • 65
  • 124