Conway's The Game of Life

28 Mar 2014

Hi guys,

New to C++, currently trying to tackle the Game of life. I've managed to initialise the screen and bring up some pixels. Im now trying to right a function that will detect pixels neighbours before I can move on to functions that will make the pixels "evolve". I've set 'n=0' and asked for return of 'n' after checking for pixels but it keeps saying

Error: Undefined symbol getPixels() (referred from Lifec.cpp.LPC1768.o).

/media/uploads/el13drt/return_type.png

29 Mar 2014

The function getPixels doesn't exist, so you cannot use it.

29 Mar 2014

/media/uploads/el13drt/funtion_return.png

Sorry, 'int' was supposed to be 'void' I'm trying to set my functions before I use them in my main loop so can then just I can just use function calls. Is this a sensible way to approach it? I've reposted a picture showing it with 'void', thanks for the reply.

31 Mar 2014

You need to show us more of the code. The error is with getPixels not being defined when you try to call it, not with getPixel not being defined. You showed us the definition but no context or where it is being called. Without seeing the context in which you are calling the function it's hard to say why it's not defined.

If you use <<code>> and then <</code>> on the lines before and after a block of code then you can paste the code here rather than including a screen shot. It allows you to post a lot more code in a far more readable format.

Also your getPixels needs to have a return type of int as initially shown, a return type of void will give you a compile error since you then try to return an int.

You also probably also check something like getPixel(i+1,j+1) doesn't go outside the edge of the display when checking the edges of the screen.

14 Apr 2014

thanks, this is what ive got

<<code>>

  1. include "mbed.h"
  2. include "N5110.h"

Dan Tomlinson 26/03/14

vcc, sce, rst, dc, mosi, clk, led N5110 lcd(p5,p6,p7,p8,p11,p13,p21); PWM for backlight

global variable

set functions void checkerBoard(); void clearCells(); void getPixels();

void getPixels(int i, int j){checks quantity of neighbours

int n=0;

if (lcd.getPixel(i-1,j-1))pixel to top-left n++; if (lcd.getPixel(i-1,j))pixel to left n++; if (lcd.getPixel(i-1,j+1))pixel to bottom-left n++; if (lcd.getPixel(i,j-1))pixel to top n++; if (lcd.getPixel(i,j+1))pixel to bottom n++; if (lcd.getPixel(i+1,j-1))pixel to top-right n++; if (lcd.getPixel(i+1,j))pixel to right n++; if (lcd.getPixel(i+1,j+1))pixel to bottom-right n++; return n; }

clears cells void clearCells(){ loop through cells and clear

for (int i=0; i<84; i++){ for (int j=0; j< 48; j++) { lcd.clearPixel(i,j); } } }

set pixels void checkerBoard(){ set pixels

for (int i = 0; i < 84 ; i+=7) { pixel axis and plots in increments of 7 for (int j = 0; j < 48 ; j+=4) { increments of 4 lcd.setPixel(i,j); } } lcd.refresh(); must refresh to write buffer display }

int main(){

lcd.init(); initialise display lcd.printString("Welcome To",15,1); display splash screen through 'x,y' pixel co-ordinates lcd.printString("Conway's",20,2); lcd.printString("Game Of life",6,3); lcd.printString("Dan Tomlinson",0,5); wait(4.0);

while(1) { checkerBoard();set pixels getPixels();

}

} <</code>>

I know that the code for searching for pixels needs an extra loop so it is able to do something with the return type but i dont know if that means my code still shouldnt be compiling at this point?

14 Apr 2014

As it is you'll get a definite compile error. getPixels is defined as having two parameters, i and j, but when you call it you don't pass any values, that will give a compile error.

In main() replace the call to getPixels() with updateImage() and then have that new function loop through calling getPixels( )

In order to ensure the new image is only calculated based on the current one you can't just update the image, you need a little buffering. Without it you'll be basing your neighboring pixels count half on old data and half on new. Easy if you have 4k of ram free, if not then you need to get a little bit sneaky.

I've probably got the rules slightly wrong but you should get the idea.

void updateImage() {
char newImage[84,48];


// generate the next image
for (int i=0; i<84; i++){
 for (int j=0; j< 48; j++){ 
  switch (getPixels(i,j) {
   case 0:
   case 1:
   case 2:
     newImage[i,j] = 0; // 0 to 2 - die
   break;
   case 3:
   case 4:
     newImage[i,j] = lcd.getPixel(i,j); // 3-4 no change
   break;
   case 5:
   case 6: 
     newImage[i,j] = 1; // 5-6 grow new one
   case 7:
   case 8:
   default:
     newImage[i,j] = 0; // 7 or more - die
  } // end switch
 } // end for
} // end for


//update the display
for (int i=0; i<84; i++){
 for (int j=0; j< 48; j++){ 
  if  (newImage[i,j])
    lcd.setPixel(i,j);
  else
    lcd.clearPixel(i,j);
  } // end for
 } // end for

} // end function updateImage

And the <<code>> and <</code>> markers need to be on lines on their own for the formatting to work correctly as above. Preview is your friend ;-)

edit: changed buffer from and int array to a char array. A stupid error, clearly you don't need 16 bits or more for a 1 or a 0.

14 Apr 2014

A lower memory solution:

Rather than buffering the whole image only buffer two rows, and then update the display 2 rows behind the point we are calculating. This means that the area of the image used for the calculations is always the old image but we don't need to hold the whole thing in memory at the same time.

I moved the switch code to decide what to do based on the result of GetPixels into a separate function since we need to call it from two different locations now.

// function to return 0 if the given pixel should be empty in the next frame, 1 if it should be solid.
int aliveOrDead(int i, int j) {
 switch (getPixels(i,j) {
  case 0:
  case 1:
    return 0;
  ...
  // etc...
  ...
  }
}


void updateImage() {

char buffer[2,48];
int bufferRow = 0;

// first two rows, just put the results in the buffer
for (int i=0; i<2; i++){
 for (int j=0; j< 48; j++){ 
    buffer[i,j] = aliveOrDead(i,j);
  }
 }
}

// next 82 rows, update the image 2 rows behind the current location and then calculate the new values.
for (int i=2; i<84; i++){
 for (int j=0; j< 48; j++){ 

  // update image
  if (buffer[bufferRow,j])
    lcd.setPixel(i-2,j);
  else
    lcd.clearPixel(i-2,j);

  // calculate the next value
  buffer[bufferRow,j] = aliveOrDead(i,j);

 } // end for j

 bufferRow++;  // update the row number in our buffer
 if (bufferRow == 2)
   bufferRow = 0;

} // end for i


// finally update the last two rows of the screen.
for (int i=82; i<84; i++){
 for (int j=0; j< 48; j++){ 
  if (buffer[i-82,j])
    lcd.setPixel(i,j);
  else
    lcd.clearPixel(i,j);
 }
}

} // end updateImage

You could further reduce memory use if needed, we only technically need to buffer 1 row and 2 items, 50 values rather than the 96 used above for simplicity. You could further reduce it by a factor of 8 by packing the bits, we only need a 1 or a 0 so we don't need a whole char for each value. That would cut the size of the buffer down to 7 bytes (a lot less than the 4096 used to hold the whole frame above) but would add a lot more complexity.

17 Apr 2014

thanks for the advice, I've pretty much completed it now. Just one thing remains, so far I've been using this 'for' loop to manually input patterns on to my display ...

void Toad(){ //Oscillator
     
     for (int i = 9; i < 12 ; i+=1) { //pixel axis and plots 
         for (int j = 42; j < 43 ; j+=1) { //increments
             lcd.setPixel(i,j);
     for (int i = 8; i < 11 ; i+=1){
         for (int j = 43; j < 44 ; j+=1){
             lcd.setPixel(i,j);
             }
             }
         }
    }
    lcd.refresh(); //must refresh to write buffer display
}

Its been a bit tedious to do it this way especially with the various patterns over and over again, surely there is another way to place pixels in to a specific desired shape. This above method worked until I tried implementing a pattern known as a Pulsar (shown below)

void Pulsar(){ //Oscillator
     
     for (int i = 57; i < 58 ; i+=1) { //first vertical plot
         for (int j = 10; j < 13 ; j+=1) {// 
             lcd.setPixel(i,j);
     for (int i = 62; i < 63 ; i+=1) { //first vertical plot
         for (int j = 10; j < 13 ; j+=1) {
             lcd.setPixel(i,j);
     
     for (int i = 64; i < 65 ; i+=1) { //second vertical plot
         for (int j = 10; j < 13 ; j+=1) {
             lcd.setPixel(i,j);
     for (int i = 69; i < 70 ; i+=1) { 
         for (int j = 10; j < 13 ; j+=1) {// second vertical plot
             lcd.setPixel(i,j);
             
     for (int i = 59; i < 62 ; i+=1) { //first horizontal plot
         for (int j = 8; j < 9 ; j+=1) {
             lcd.setPixel(i,j);
     for (int i = 65; i < 68 ; i+=1) { //first horizontal plot
         for (int j = 8; j < 9 ; j+=1) {
             lcd.setPixel(i,j);        
    
     for (int i = 59; i < 62 ; i+=1) { //second horizontal plot
         for (int j = 13; j < 14 ; j+=1) {
             lcd.setPixel(i,j);
     for (int i = 65; i < 68 ; i+=1) { //second horizontal plot
         for (int j = 13; j < 14 ; j+=1) {
             lcd.setPixel(i,j);        
             
     for (int i = 59; i < 62 ; i+=1) { //third horizontal plot
         for (int j = 15; j < 16 ; j+=1) {
             lcd.setPixel(i,j);
     for (int i = 65; i < 68 ; i+=1) { //third horizontal plot
         for (int j = 15; j < 16 ; j+=1) {
             lcd.setPixel(i,j);      
         
     for (int i = 57; i < 58 ; i+=1) { //third vertical plot
         for (int j = 17; j < 20 ; j+=1) {
             lcd.setPixel(i,j);
     for (int i = 62; i < 63 ; i+=1) { //third vertical plot
         for (int j = 17; j < 20 ; j+=1) {
             lcd.setPixel(i,j);   
             
     for (int i = 59; i < 62 ; i+=1) { //fourth horizontal plot
         for (int j = 21; j < 22 ; j+=1) {
             lcd.setPixel(i,j);
     for (int i = 65; i < 68 ; i+=1) { //fourth horizontal plot
         for (int j = 21; j < 22 ; j+=1) {
             lcd.setPixel(i,j);       
     
     for (int i = 64; i < 65 ; i+=1) { //fourth vertical plot
         for (int j = 17; j < 20 ; j+=1) {
             lcd.setPixel(i,j);
     for (int i = 69; i < 70 ; i+=1) { //fourth vertical plot
         for (int j = 17; j < 20 ; j+=1) {
             lcd.setPixel(i,j);        
        
         }}}}}}}}}
         }}}}}}}}}
    }}}}}}}}
    }}}}}
    
    lcd.refresh(); //must refresh to write buffer display
}
}

When I call this function it takes 5 seconds for it to change its pattern (compared to the 40ms it should be taking)which in turn also brings my other oscillating shapes to almost stand still. I'm sure this is down to the amount of data I'm using for just a single shape... how can i resolve this?

17 Apr 2014

This code appears to have every lower for loop inside the one above (outer loop). The total number of iterations is then huge - 5 sec is not a surprise. Perhaps you just need to check your } placement.

18 Apr 2014

Thanks, works perfectly now :)

22 Apr 2014

A couple of coding style points. ( Note this is pure nitpicking, feel free to ignore completely )

1) If there is only one statement inside a loop or if then you don't technically need the { }'s. Another for loop or if statement, no matter how many lines it contains, counts as a single statement. Which means that none of those for loops for the pulsar code need { } at all, you could simply have removed them and it would have worked perfectly.

Whether to include the brackets or not is a personal coding style issue, some people always put them in, some people always skip them when not needed, some do a bit of both. Personally I'll normally include them for clarity reasons except for situations like your pulsar code above where it's fairly clear what is supposed to be happening all they do is add clutter and the chance of misplacing one causing an error.

2) in for loops it's more normal to use i++ rather than i += 1, absolutely no difference in what the computer does, it just makes it more obvious that you are increasing in steps of one rather intending to do += 2 and hitting the wrong key etc...

3) for something like your pulsar code or your toad code I'd be tempted to do it along the lines of

#define _xSize_ 84
#define _ySize_ 48

void Toad(int x, int y){ //Oscillator

// check we aren't going to try to draw off the edge of the screen
  if (x < 2)
    return;
  if (y < 1)
    return;
  if (x >= (_xsize_ - 2)
    return;
  if (y >= (_ysize_ - 1)
    return;

// draw the shape
     for (int i = x - 1 ; i < x + 2; i++) 
         for (int j = y - 1; j < y ; j++) 
             lcd.setPixel(i,j);

     for (int i = x-2; i < x+1 ; i++)
         for (int j = y ; j < j+ 1 ; j++)
             lcd.setPixel(i,j);

    lcd.refresh(); //must refresh to write buffer display
}

You can then call toad(x,y) with the x and y locations for the center of the shape rather than having to go in and manually change numbers if you want to move things.

You should always aim to avoid hard coded numbers anywhere in the code other than things like 0 or -1, they make it far harder to maintain the code if something like the screen size changes. If you do need to have some fixed value either define it as a macro (as above) or a global const if you don't like macros. That way you only have to change one line of code if the value ever changes.

In terms of simplifying your drawing for complex shapes you could define a set of functions for simple shapes at arbitrary locations e.g.

void drawHorizontalLine(int xStart, int length, int y) {

// sanity check, we are staying on the screen
 if (y < 0)
  return;
 if (y>=_ySize_)
  return;

 int currentX = xStart;

 int endPoint = xStart + length;

 if (currentX  < 0)
   currentX  = 0;
 if (endPoint  >= _xSize_)
  endPoint   = _xSize_ - 1;
 
// draw the line
 while (currentX < endPoint) {
   lcd.setPixel(currentX, y)
   currentX++;
 }

 // no refresh here since this is part of a basic building block not a final shape.
}

You then use those building blocks to put your complex shape together. e.g. toad becomes:

void Toad(int x, int y){ // draw a toad centered around x,y (x-1 to x+2, y to y+1 since 4 x 2 doesn't have an integer center)
  drawHorizontalLine(x-1,3,y);
  drawHorizontalLine(x,3,y+1);
  lcd.refresh(); 
}

Horizontal and Vertical lines, possibly diagonal lines and things like hollow boxes or + shapes of specified sizes are easy to code like this and should be all you need to make most shapes in a far more user friendly way than you currently are.

22 Apr 2014

Seeing as I'm new to this, all advice is welcome. So thanks and ill give it a go.

23 Apr 2014

One minor correction to my code above: in drawHorizontalLine(...) it should be

if (endPoint  > _xSize_)
  endPoint   = _xSize_;

Otherwise it will always stop 1 pixel short of the edge of the screen.

24 Apr 2014

Reading your discussion inspired me - so I lifted some of the work from above and created a version you might find interesting.

12 May 2014

Thanks, sounds interesting. I'll be sure to check it out once I've completed my end of year exams, good to see this conversation has inspired someone.