LOLShield game of life

Hi there

I programmed Conways game of life on the LOLshield.

Now the game seems to work, I can tell by the following situation:

Sometimes you end up by a static block of 4 or an oscillating row of 3 led's. This is OK by the rules, and tells me that the rules themselves should be ok.

However: I have strange things happening on the edges of the screen. Oftentimes I end up with 2 neighboring LED's on the screens edge that stay on.

By the rules, if one cell has only one neighbor, as it is the case when only 2 led's are shining, they both should die.

Now the strange thing is, if I have just 2 led's in the middle of the screen turned on, they die off correctly.

Now below, I post the code.

I am aware that I have no code to account for the screen edges. Meaning if a cell is at position 14, it will look at the non-existing position 15 for a neighbor, instead of going back to position 1 to look there.

I will write that part later (because I haven't figured it out yet). Still, if, like in my code, the neighbor-check looks into non-existent cells, neighbors shouldn't be counted up, because those cells don't exist as they are out of bound).

Now I'll be pleased with both answers:

  • If you have a suggestion how to improve my neighbor-check, so that it works on the edges as if the screen was wrapped, I'd be VERY pleased.
  • If anyone knows why I even end up in that strange situation and can explain this, I might be able to figure the rest out myself. :open_mouth:

So here it is:

#include <Charliplexing.h>
#include <Figure.h>
#include <Font.h>

const byte SIZEX = 14;
const byte SIZEY = 9;

byte grid[SIZEX][SIZEY];
byte nextGrid[SIZEX][SIZEY];

byte neighbors;

void setup(){

 Serial.begin(9600);
  
 LedSign::Init(); //Initialize LOLShield  
 LedSign::SetBrightness(40);
 
 randomSeed(analogRead(0)); //Initialize Random Number

 clearScreen();
 
 initialize();
 
 delay(200);
 
}

void loop(){

clearScreen();

gamelogic();

showGame();

fillnextgrid();

delay(200);

    
}  


//Empty Screen
void clearScreen(){
  
 LedSign::Clear();
 
}

//Initializes the very first screen with random cells
void initialize(){

  for(int x = 0; x < SIZEX;x++){
  
     for(int y = 0; y < SIZEY;y++){

      grid[x][y] = random(2);
    
      LedSign::Set(x,y,grid[x][y]);
    }    
  }  
}


//Needs logic for edge boundaries!!
void gamelogic(){

  for(int x = 0; x < SIZEX; x++){
  
     for(int y = 0; y < SIZEY; y++){

         
         //Count all the neighboring cells
         neighbors = 0;

         if(grid[x-1][y-1] == 1) neighbors++;  //Nowthwest
         if(grid[x][y-1] == 1) neighbors++;    //north
         if(grid[x+1][y-1] == 1) neighbors++;  //northeast 
         if(grid[x-1][y] == 1) neighbors++;    //west
         if(grid[x+1][y] == 1) neighbors++;    //east
         if(grid[x-1][y+1] == 1) neighbors++;  //southwest
         if(grid[x][y+1] == 1) neighbors++;    //south
         if(grid[x+1][y+1] == 1) neighbors++;  //southeast 


         // The gamerules
         
         // born
         if(grid[x][y] == 0 && neighbors == 3) nextGrid[x][y] = 1;


         // Alive and stay alive
         if(grid[x][y] == 1 && neighbors == 2) nextGrid[x][y] = 1;
         if(grid[x][y] == 1 && neighbors == 3) nextGrid[x][y] = 1;

         // Alive and die
         if(grid[x][y] == 1 && neighbors < 2) nextGrid[x][y] = 0;
         if(grid[x][y] == 1 && neighbors > 3) nextGrid[x][y] = 0;

         

         //Debugging the neighbor count
         Serial.println(neighbors);
         //delay(10);
         
       
    }    
  }   
}

//Writes back all the values that gamelogic() calculated into temporary nextGrid to grid
void fillnextgrid(){

  for(int x = 0; x < SIZEX; x++){
  
     for(int y = 0; y < SIZEY; y++){

        grid[x][y] = nextGrid[x][y];
       
    }    
  }   
}


//Shows the gamescreen
void showGame(){

  for(int x = 0; x < SIZEX; x++){
  
     for(int y = 0; y < SIZEY; y++){
    
      LedSign::Set(x,y,grid[x][y]);
      
    }    
  }  
}

Has anyone some advice for me? I'm really kinda stuck. :confused:

Write a function that accesses neighbors, and performs the wrap with modular arithmetic. Then you can call the same function for all 8 neighbors.

The usual arrangement for the screen edges is that they fold over to the opposite side.

Otherwise there are no really valid rules for them. Folding over of course, increases interaction as what travels off one edge comes back in the other and likely impacts any other object present.

It's acceptable to create an empty boundary of size one, around the array. You only calculate the inside cells, so no new ones are ever created in the boundary. It's actually neat, because sometimes some "border creatures" end up living around the edges and in the corners. :slight_smile:

Write a function that accesses neighbors, and performs the wrap with modular arithmetic. Then you can call the same function for all 8 neighbors.

Can you clarify please? I don't have the slightest clue how this can be done, but it sounds exactly like what I want to end up with. To wrap the edge (say the right one) to the left one and so on.

Kind of the 'snake' game, if it leaves one edge, it enters the opposite one.

Can you maybe post a snippet so I can build this? I would really appreciate it. Thank you.

So for anyone else struggling with this...

If you own the LOLShield, you have to import the library for it, and then you'll have an example of the game of life there [File -> Examples -> LOLShield -> LOLshield_Life]

I took the neighbour-counting code form there hand have a working version now.

Here's the whole code:

/*Working Game of Life
 * Just one run! No checks yet if the game finished (e.g. empty screen or oscillator)
*/

#include <Charliplexing.h>
#include <Figure.h>
#include <Font.h>

const byte SIZEX = 14;
const byte SIZEY = 9;

byte grid[SIZEX][SIZEY];
byte nextGrid[SIZEX][SIZEY];

byte neighbors;

byte difference = 0;

void setup(){

 Serial.begin(9600);
  
 LedSign::Init(); //Initialize LOLShield  
 LedSign::SetBrightness(40);
 
 randomSeed(analogRead(0)); //Initialize Random Number

 clearScreen();
 
 initialize();
 
 delay(200);
 
}

void loop(){

clearScreen();

gamelogic();

showGame();

fillnextgrid();

//checksamegrid();

delay(150);

    
}  


//Empty Screen
void clearScreen(){
  
 LedSign::Clear();
 
}

//Initializes the very first screen with random cells
void initialize(){

  for(int x = 0; x < SIZEX;x++){
  
     for(int y = 0; y < SIZEY;y++){

      grid[x][y] = random(2);
    
      LedSign::Set(x,y,grid[x][y]);
    }    
  }  
}


//Needs logic for edge boundaries!!
void gamelogic(){

  for(int x = 0; x < SIZEX; x++){
  
     for(int y = 0; y < SIZEY; y++){

         
         //Count all the neighboring cells
         byte count = neighbours(x, y);


         // The gamerules
         
         // born
         if(grid[x][y] == 0 && count == 3) nextGrid[x][y] = 1;


         // Alive and stay alive
         if(grid[x][y] == 1 && count == 2) nextGrid[x][y] = 1;
         if(grid[x][y] == 1 && count == 3) nextGrid[x][y] = 1;

         // Alive and die
         if(grid[x][y] == 1 && count < 2) nextGrid[x][y] = 0;
         if(grid[x][y] == 1 && count > 3) nextGrid[x][y] = 0;

         

         //Debugging the neighbor count
         //Serial.println(neighbors);
         //delay(10);
         
       
    }    
  }   
}

//Writes back all the values that gamelogic() calculated into temporary nextGrid-array to grid-array
void fillnextgrid(){

  for(int x = 0; x < SIZEX; x++){
  
     for(int y = 0; y < SIZEY; y++){

        grid[x][y] = nextGrid[x][y];
       
    }    
  }   
}

byte neighbours(byte x, byte y) {
  return grid[(x + 1) % SIZEX][y] + 
    grid[x][(y + 1) % SIZEY] + 
    grid[(x + SIZEX - 1) % SIZEX][y] + 
    grid[x][(y + SIZEY - 1) % SIZEY] + 
    grid[(x + 1) % SIZEX][(y + 1) % SIZEY] + 
    grid[(x + SIZEX - 1) % SIZEX][(y + 1) % SIZEY] + 
    grid[(x + SIZEX - 1) % SIZEX][(y + SIZEY - 1) % SIZEY] + 
    grid[(x + 1) % SIZEX][(y + SIZEY - 1) % SIZEY];
}

void checksamegrid(){
  
  for(int x = 0; x < SIZEX; x++){
  
     for(int y = 0; y < SIZEY; y++){
        
        if (grid[x][y] == nextGrid[x][y]){}
        else{
          difference++;
          Serial.println(difference);
        }

    }    
  }
  if (difference == 0){
    initialize();
    Serial.println(difference);   
    difference = 0;
  }
}

        

//Shows the gamescreen
void showGame(){

  for(int x = 0; x < SIZEX; x++){
  
     for(int y = 0; y < SIZEY; y++){
    
      LedSign::Set(x,y,grid[x][y]);
      
    }    
  }  
}

I do realize that this is the pragmatic approach, but after reading Wikipedia for a while and watching Youtube for a bit, I understood what modulo is and does.
However, implementing it in code is a different story.
So, looking at this particular snippet....

byte neighbours(byte x, byte y) {
  return grid[(x + 1) % SIZEX][y] + 
    grid[x][(y + 1) % SIZEY] + 
    grid[(x + SIZEX - 1) % SIZEX][y] + 
    grid[x][(y + SIZEY - 1) % SIZEY] + 
    grid[(x + 1) % SIZEX][(y + 1) % SIZEY] + 
    grid[(x + SIZEX - 1) % SIZEX][(y + 1) % SIZEY] + 
    grid[(x + SIZEX - 1) % SIZEX][(y + SIZEY - 1) % SIZEY] + 
    grid[(x + 1) % SIZEX][(y + SIZEY - 1) % SIZEY];
}

....I thought it's rather efficient to take it than to re-invent it, as it's fairly complicated and one mistake there ruins the whole thing anyways and would be hell to debug. :roll_eyes:

I also kept the gamerules intentionally simple, even tough I could merge some of them with logical operators.

         // The gamerules
         
         // born
         if(grid[x][y] == 0 && count == 3) nextGrid[x][y] = 1;


         // Alive and stay alive
         if(grid[x][y] == 1 && count == 2) nextGrid[x][y] = 1;
         if(grid[x][y] == 1 && count == 3) nextGrid[x][y] = 1;

         // Alive and die
         if(grid[x][y] == 1 && count < 2) nextGrid[x][y] = 0;
         if(grid[x][y] == 1 && count > 3) nextGrid[x][y] = 0;

Anyhow, thank everyone for looking into this. :slight_smile:

I hope my own code can help someone out who prefers a more simple approach to this whole "Conways game of life"-thing. I commented every step and kept it intentionally simple to read.

So this project leaves kind of a bitter taste, because I couldn't do it all by myself.

This is my final version.

It re-initializes when the screen runs empty or if there are just oscillating forms left.
Also, the initialisation is more visually appealing.

/*Working Game of Life
 * Resets itself if everything dies off, or if ther is one or more simple oscillating form left
*/

#include <Charliplexing.h>
#include <Figure.h>
#include <Font.h>

#define Delay 200

const byte SIZEX = 14;
const byte SIZEY = 9;

byte grid[SIZEX][SIZEY];
byte nextGrid[SIZEX][SIZEY];

byte difference = 0;
byte lastdifference = 0;
byte boring = 0;
byte alive = 0;
byte lastalive = 0;

void setup(){

 Serial.begin(9600);
  
 LedSign::Init(); //Initialize LOLShield  
 LedSign::SetBrightness(40);
 
 randomSeed(analogRead(0)); //Initialize Random Number

 clearScreen();
 
 initialize();
 
 delay(Delay);
 
}

void loop(){

  difference = 0;
  alive = 0;

  clearScreen();

  lifecycle();

  showGame();

  fillnextgrid();

  checksamegrid();

  delay(Delay);
    
}  


//Empty Screen
void clearScreen(){
  
 LedSign::Clear();
 
}

//Initializes the very first screen with random cells
void initialize(){

  clearScreen();

  //Turn ALL Led's on counting up
  for(int x = 0; x < SIZEX;x++){
  
     for(int y = 0; y < SIZEY;y++){

      grid[x][y] = 1;
      delay(15);
      LedSign::Set(x,y,grid[x][y]);
    }    
  }

  //Turn SOME Led's off counting up
  for(int x = 0; x < SIZEX;x++){
  
     for(int y = 0; y < SIZEY;y++){

        grid[x][y] = random(2);
        LedSign::Set(x,y,grid[x][y]);
        delay(20);
     }
  }
  delay(Delay * 5);    
}


void lifecycle(){

  for(int x = 0; x < SIZEX; x++){
  
     for(int y = 0; y < SIZEY; y++){

         
         //Count all the neighboring cells
         byte count = neighbours(x, y);


         // The gamerules
         
         // born
         if(grid[x][y] == 0 && count == 3){
          nextGrid[x][y] = 1;
          difference++;
          alive++;
         }


         // Alive and stay alive
         if(grid[x][y] == 1 && count == 2 || count == 3){
          nextGrid[x][y] = 1;
          alive++;
         }
         

         // Alive and die
         if(grid[x][y] == 1 && count < 2 || count > 3){
          nextGrid[x][y] = 0;
          difference++;
          alive--;
         }
          

         //Debugging the neighbor count
         //Serial.println(count);
         //delay(10);
         
       
    }    
  }   
}

//Writes back all the values that lifecycle() calculated into the temporary 'nextGrid-array' into 'grid-array'
void fillnextgrid(){

  for(int x = 0; x < SIZEX; x++){
  
     for(int y = 0; y < SIZEY; y++){

        grid[x][y] = nextGrid[x][y];
       
    }    
  }   
}

//Counts the neighbours and gives back the number of neighbours
byte neighbours(byte x, byte y) {
  return grid[(x + 1) % SIZEX][y] + 
    grid[x][(y + 1) % SIZEY] + 
    grid[(x + SIZEX - 1) % SIZEX][y] + 
    grid[x][(y + SIZEY - 1) % SIZEY] + 
    grid[(x + 1) % SIZEX][(y + 1) % SIZEY] + 
    grid[(x + SIZEX - 1) % SIZEX][(y + 1) % SIZEY] + 
    grid[(x + SIZEX - 1) % SIZEX][(y + SIZEY - 1) % SIZEY] + 
    grid[(x + 1) % SIZEX][(y + SIZEY - 1) % SIZEY];
}


//Checks if the grid is the same. It does so by ckecking if a cell is born or has died (difference),
//and by comparing the alive count to a previous value.
void checksamegrid(){
  
  if (difference == lastdifference && alive == lastalive) boring++;  

  if (boring >= 25){
    delay(Delay * 3);
    initialize();
    boring = 0;
  }
  
  lastdifference = difference;
  lastalive = alive;

  //if the grid is completely empty, no need to wait for 'boring' to count up. initialize immediately.
  int emptygrid = 0;
  
  for(int x = 0; x < SIZEX; x++){
     
    for(int y = 0; y < SIZEY; y++){
      
      emptygrid = emptygrid + grid[x][y];  
    }
  }
  
  if (emptygrid == 0){   
    clearScreen();
    delay(Delay * 3);
    initialize();
  }

  //Serial.println(alive);

}
  


        

//Shows the gamescreen
void showGame(){

  for(int x = 0; x < SIZEX; x++){
  
     for(int y = 0; y < SIZEY; y++){
    
      LedSign::Set(x,y,grid[x][y]);
      
    }    
  }  
}