Conways Game of Life (+ TFT)

strawberrymaker
Wed May 20, 2015 6:23 pm
What is Game of Life?
Well, worked on this little Game yesterday.
Its basically Conways Game of Life on a 50×33 Grid, which atm. must be predefined in the sketch.
The Output is visible on a ili9341-screen, but may i’ll try it with the oled too.
It’s not that of a big project, but still a little bit funny :) Going to add somekind of input or touch capability this weekend.
Download(Puush)
Preloaded with gospers glider gun ^^

multi is for the multiplicator, which scales the grid up. maxY and MaxX stand for the grid sizes (which may also needs to be changed)

I tried with 80×80, but its still “slow”. 100×100 didnt worked, 4000bytes too big for the ram (was the error). One way would be saving the grid to an external eeprom.

~Straw


PaulRB
Wed Jun 24, 2015 10:32 pm
Hi Straw. Game of life is one of my obsessions. Here is my attempt using an OLED displaying Gosper’s Glider Gun on a grid of 128×64 running on Maple Mini clone. It’s pretty furious! Around 580 generations per second.

Memory is not a problem if you store your grid of cells packed into bits:
Sketch uses 14,788 bytes (13%) of program storage space. Maximum is 110,592 bytes.
Global variables use 4,256 bytes of dynamic memory.


PaulRB
Sun Jun 28, 2015 5:29 pm
OK, I got the 256×256 grid running. The 128×64 window provided by the OLED screen can be scrolled around using a mini joystick.

Video

// Conway's Game Of Life 256x256
// PaulRB
// Jun 2015

#include <SPI.h>

//Pins controlling SSD1306 Graphic OLED
#define OLED_DC 1
#define OLED_CS 0
#define OLED_RESET 2
#define OLED_COLS 128
#define OLED_ROWS 8

#define MATRIX_COLS 256
#define MATRIX_ROWS 32

union MatrixData {
unsigned long long l[MATRIX_ROWS/8];
byte b[MATRIX_ROWS];
};

#define SW_U 17
#define SW_D 20
#define SW_L 18
#define SW_R 16
#define SW_C 19

int scrollHorz = 0;
int scrollVert = 0;

MatrixData Matrix[MATRIX_COLS+1]; // Cell data in ram

void setup() {

pinMode(OLED_DC, OUTPUT);
pinMode(OLED_CS, OUTPUT);
pinMode(OLED_RESET, OUTPUT);

SPI.begin();
SPI.setBitOrder(MSBFIRST); // Set the SPI bit order
SPI.setDataMode(SPI_MODE0); //Set the SPI data mode 0
SPI.setClockDivider(SPI_CLOCK_DIV2); // 2.25 MHz

digitalWrite(OLED_RESET, HIGH);
delay(1);
digitalWrite(OLED_RESET, LOW);
delay(10);
digitalWrite(OLED_RESET, HIGH);

digitalWrite(OLED_DC, LOW);
digitalWrite(OLED_CS, LOW);

SPI.transfer(0xAE); // Display off
SPI.transfer(0xD5); // Set display clock divider
SPI.transfer(0x80);
SPI.transfer(0xA8); // Set multiplex
SPI.transfer(0x3F);
SPI.transfer(0xD3); // Set display offset
SPI.transfer(0x00);
SPI.transfer(0x40); // Set start line to zero
SPI.transfer(0x8D); // Set charge pump
SPI.transfer(0x14);
SPI.transfer(0x20); // Set memory mode
SPI.transfer(0x00);
SPI.transfer(0xA0 | 0x1); // Set segment remapping
SPI.transfer(0xC8); // Set command Scan decode
SPI.transfer(0xDA); // Set Comm pins
SPI.transfer(0x12);
SPI.transfer(0x81); // Set contrast
SPI.transfer(0xCF);
SPI.transfer(0xd9); // Set precharge
SPI.transfer(0xF1);
SPI.transfer(0xDB); // Set Vcom detect
SPI.transfer(0x40);
SPI.transfer(0xA4); // Allow display resume
SPI.transfer(0xA6); // Set normal display
SPI.transfer(0xAF); // Display On

digitalWrite(OLED_CS, HIGH);

//R-pentomino
//Matrix[64].l[4] = 0b0000010;
//Matrix[65].l[4] = 0b0000111;
//Matrix[66].l[4] = 0b0000100;

//Gosper's Glider Gun
//Matrix[64].l[4] = 0b00000000000000000000000000010000000000000000000000;
//Matrix[65].l[4] = 0b00000000000000000000000001010000000000000000000000;
//Matrix[66].l[4] = 0b00000000000000011000000110000000000001100000000000;
//Matrix[67].l[4] = 0b00000000000000100010000110000000000001100000000000;
//Matrix[68].l[4] = 0b00011000000001000001000110000000000000000000000000;
//Matrix[69].l[4] = 0b00011000000001000101100001010000000000000000000000;
//Matrix[70].l[4] = 0b00000000000001000001000000010000000000000000000000;
//Matrix[71].l[4] = 0b00000000000000100010000000000000000000000000000000;
//Matrix[72].l[4] = 0b00000000000000011000000000000000000000000000000000;

randomiseMatrix();
outputMatrix();

Serial.begin(115200);
pinMode(SW_U, INPUT_PULLUP);
pinMode(SW_D, INPUT_PULLUP);
pinMode(SW_L, INPUT_PULLUP);
pinMode(SW_R, INPUT_PULLUP);
pinMode(SW_C, INPUT_PULLUP);

}

void loop() {
unsigned long start = millis();
for (int i=0; i<1000; i++) {
generateMatrix();
outputMatrix();
}
//Serial.print("Gens/s:"); Serial.println(1000000UL/(millis() - start));

}

void outputMatrix() {

digitalWrite(OLED_DC, LOW); //Command mode
digitalWrite(OLED_CS, LOW); //Enable display on SPI bus

SPI.transfer(0x21); // Set column address
SPI.transfer(0);
SPI.transfer(OLED_COLS-1);

SPI.transfer(0x22); // Set page address
SPI.transfer(0);
SPI.transfer(OLED_ROWS-1);

digitalWrite(OLED_CS, HIGH); //Disable display on SPI bus

digitalWrite(OLED_DC, HIGH); // Data mode
digitalWrite(OLED_CS, LOW); //Enable display on SPI bus

if (digitalRead(SW_L) == LOW && scrollHorz < (MATRIX_COLS - OLED_COLS)) scrollHorz++;
if (digitalRead(SW_R) == LOW && scrollHorz > 0) scrollHorz--;
if (digitalRead(SW_U) == LOW && scrollVert < ((MATRIX_ROWS - OLED_ROWS)<<3)) scrollVert++;
if (digitalRead(SW_D) == LOW && scrollVert > 0) scrollVert--;

//Send matrix data for display on OLED
for (int col = 0; col < OLED_ROWS; col++) {
int colScrolled = col + (scrollVert>>3);
for (int row = 0; row < OLED_COLS; row++) {
SPI.transfer(Matrix[row+scrollHorz].b[colScrolled]);
}
}
digitalWrite(OLED_CS, HIGH);
}

void randomiseMatrix() {

//Set up initial cells in matrix
randomSeed(analogRead(0));
for (int row = 0; row < MATRIX_COLS; row++) {
for (int col = 0; col < MATRIX_ROWS; col++) {
Matrix[row].b[col] = random(0xff);
}
}
}

void injectGlider() {

int col = random(MATRIX_COLS);
int row = random(MATRIX_ROWS);
Matrix[col++].b[row] |= 0b0000111;
Matrix[col++].b[row] |= 0b0000001;
Matrix[col++].b[row] |= 0b0000010;

}

void rotateLeft(unsigned long long x[]) {
unsigned long long c = 0;
for (int i=0; i<4; i++) {
unsigned long long c2 = x[i] >> 63;
x[i] = x[i] << 1 | c;
c = c2;
}
x[0] |= c;
}

void rotateRight(unsigned long long x[]) {
unsigned long long c = 0;
for (int i=3; i>=0; i--) {
unsigned long long c2 = x[i] << 63;
x[i] = x[i] >> 1 | c;
c = c2;
}
x[3] |= c;
}

int generateMatrix() {

//Variables holding data on neighbouring cells
MatrixData NeighbourN, NeighbourNW, NeighbourNE, CurrCells, NeighbourW, NeighbourE, NeighbourS, NeighbourSW, NeighbourSE;

//Variables used in calculating new cells
unsigned long long tot1, carry, tot2, tot4, NewCells;

int changes = 0; // counts the changes in the matrix
static int prevChanges[4]; // counts the changes in the matrix on prev 4 generations
static int staleCount = 0; // counts the consecutive occurrances of the same number of changes in the matrix

//set up N, NW, NE, W & E neighbour data
NeighbourN = Matrix[MATRIX_COLS-1];
CurrCells = Matrix[0];
Matrix[MATRIX_COLS] = CurrCells; // copy row 0 to location after last row to remove need for wrap-around code in the loop

NeighbourNW = NeighbourN;
rotateLeft(NeighbourNW.l);
NeighbourNE = NeighbourN;
rotateRight(NeighbourNE.l);

NeighbourW = CurrCells;
rotateLeft(NeighbourW.l);
NeighbourE = CurrCells;
rotateRight(NeighbourE.l);

//Process each row of the matrix
for (int row = 0; row < MATRIX_COLS; row++) {

//Pick up new S, SW & SE neighbours
NeighbourS = Matrix[row + 1];

NeighbourSW = NeighbourS;
rotateLeft(NeighbourSW.l);

NeighbourSE = NeighbourS;
rotateRight(NeighbourSE.l);

for (int i=0; i<MATRIX_ROWS/8; i++) {
//Count the live neighbours (in parallel) for the current row of cells
//However, if total goes over 3, we don't care (see below), so counting stops at 4
tot1 = NeighbourN.l[i];
tot2 = tot1 & NeighbourNW.l[i]; tot1 = tot1 ^ NeighbourNW.l[i];
carry = tot1 & NeighbourNE.l[i]; tot1 = tot1 ^ NeighbourNE.l[i]; tot4 = tot2 & carry; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourW.l[i]; tot1 = tot1 ^ NeighbourW.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourE.l[i]; tot1 = tot1 ^ NeighbourE.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourS.l[i]; tot1 = tot1 ^ NeighbourS.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourSW.l[i]; tot1 = tot1 ^ NeighbourSW.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourSE.l[i]; tot1 = tot1 ^ NeighbourSE.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;

//Calculate the updated cells:
// <2 or >3 neighbours, cell dies
// =2 neighbours, cell continues to live
// =3 neighbours, new cell born
NewCells = (CurrCells.l[i] | tot1) & tot2 & ~ tot4;

//Have any cells changed?
if (NewCells != CurrCells.l[i]) {
//Count the change for "stale" test
changes++;
Matrix[row].l[i] = NewCells;
}
}

//Current cells (before update), E , W, SE, SW and S neighbours become
//new N, NW, NE, E, W neighbours and current cells for next loop
NeighbourN = CurrCells;
NeighbourNW = NeighbourW;
NeighbourNE = NeighbourE;
NeighbourE = NeighbourSE;
NeighbourW = NeighbourSW;
CurrCells = NeighbourS;
}

if (changes != prevChanges[0] && changes != prevChanges[1] && changes != prevChanges[2] && changes != prevChanges[3]) {
staleCount = 0;
}
else {
staleCount++; //Detect "stale" matrix
}

if (staleCount > 64) injectGlider(); //Inject a glider
//Serial.println(changes);

for (int i=3; i>0; i--) {
prevChanges[i] = prevChanges[i-1];
}

prevChanges[0] = changes;
}


PaulRB
Sat Jul 04, 2015 6:33 pm
And now the mini joystick is removed and the view is automatically panned around the grid to try to show a part where something interesting is going on.

Video

Code:
// Conway's Game Of Life 256x256
// PaulRB
// Jun 2015

#include <SPI.h>

//Pins controlling SSD1306 Graphic OLED
#define OLED_DC 1
#define OLED_CS 0
#define OLED_RESET 2
#define OLED_COLS 128
#define OLED_ROWS 8

#define MATRIX_COLS 256
#define MATRIX_ROWS 32

union MatrixData {
unsigned long long l[MATRIX_ROWS/8];
byte b[MATRIX_ROWS];
};

MatrixData Matrix[MATRIX_COLS+1]; // Cell data in ram

int leftChanges, rightChanges, topChanges, bottomChanges;

void setup() {

byte startupCodes[] = {
0xAE, // Display off
0xD5, // Set display clock divider
0x80,
0xA8, // Set multiplex
0x3F,
0xD3, // Set display offset
0x00,
0x40, // Set start line to zero
0x8D, // Set charge pump
0x14,
0x20, // Set memory mode
0x00,
0xA1, // Set segment remapping
0xC8, // Set command Scan decode
0xDA, // Set Comm pins
0x12,
0x81, // Set contrast
0xCF,
0xd9, // Set precharge
0xF1,
0xDB, // Set Vcom detect
0x40,
0xA4, // Allow display resume
0xA6, // Set normal display
0xAF // Display On
};

pinMode(OLED_DC, OUTPUT);
pinMode(OLED_CS, OUTPUT);
pinMode(OLED_RESET, OUTPUT);

SPI.begin();
SPI.setBitOrder(MSBFIRST); // Set the SPI bit order
SPI.setDataMode(SPI_MODE0); //Set the SPI data mode 0
SPI.setClockDivider(SPI_CLOCK_DIV2); // 2.25 MHz

digitalWrite(OLED_RESET, HIGH);
delay(1);
digitalWrite(OLED_RESET, LOW);
delay(10);
digitalWrite(OLED_RESET, HIGH);

digitalWrite(OLED_DC, LOW);
digitalWrite(OLED_CS, LOW);

for (int i = 0; i < sizeof(startupCodes); i++) SPI.transfer(startupCodes[i]);

digitalWrite(OLED_CS, HIGH);

//R-pentomino
//Matrix[64].l[4] = 0b0000010;
//Matrix[65].l[4] = 0b0000111;
//Matrix[66].l[4] = 0b0000100;

//injectGlider();

//Gosper's Glider Gun
//Matrix[64].l[4] = 0b00000000000000000000000000010000000000000000000000;
//Matrix[65].l[4] = 0b00000000000000000000000001010000000000000000000000;
//Matrix[66].l[4] = 0b00000000000000011000000110000000000001100000000000;
//Matrix[67].l[4] = 0b00000000000000100010000110000000000001100000000000;
//Matrix[68].l[4] = 0b00011000000001000001000110000000000000000000000000;
//Matrix[69].l[4] = 0b00011000000001000101100001010000000000000000000000;
//Matrix[70].l[4] = 0b00000000000001000001000000010000000000000000000000;
//Matrix[71].l[4] = 0b00000000000000100010000000000000000000000000000000;
//Matrix[72].l[4] = 0b00000000000000011000000000000000000000000000000000;

randomiseMatrix();
outputMatrix();

//Serial.begin(115200);
//pinMode(SW_U, INPUT_PULLUP);
//pinMode(SW_D, INPUT_PULLUP);
//pinMode(SW_L, INPUT_PULLUP);
//pinMode(SW_R, INPUT_PULLUP);
//pinMode(SW_C, INPUT_PULLUP);

}

void loop() {

unsigned long start = millis();

for (int i=0; i<1000; i++) {

generateMatrix();

if (leftChanges - rightChanges > 16) scrollRight();
if (rightChanges - leftChanges > 16) scrollLeft();
if (bottomChanges - topChanges > 16) scrollDown();
if (topChanges - bottomChanges > 16) scrollUp();

outputMatrix();

}
//Serial.print("Gens/s:"); Serial.println(1000000UL/(millis() - start));

}

void outputMatrix() {

digitalWrite(OLED_DC, LOW); //Command mode
digitalWrite(OLED_CS, LOW); //Enable display on SPI bus

SPI.transfer(0x21); // Set column address
SPI.transfer(0);
SPI.transfer(OLED_COLS-1);

SPI.transfer(0x22); // Set page address
SPI.transfer(0);
SPI.transfer(OLED_ROWS-1);

digitalWrite(OLED_CS, HIGH); //Disable display on SPI bus

digitalWrite(OLED_DC, HIGH); // Data mode
digitalWrite(OLED_CS, LOW); //Enable display on SPI bus

//Send matrix data for display on OLED
for (int col = MATRIX_ROWS/2 - OLED_ROWS/2; col < MATRIX_ROWS/2 + OLED_ROWS/2; col++) {
for (int row = MATRIX_COLS/2 - OLED_COLS/2; row < MATRIX_COLS/2 + OLED_COLS/2; row++) {
SPI.transfer(Matrix[row].b[col]);
}
}
digitalWrite(OLED_CS, HIGH);
}

void randomiseMatrix() {

//Set up initial cells in matrix
randomSeed(analogRead(0));
for (int row = 0; row < MATRIX_COLS; row++) {
for (int col = 0; col < MATRIX_ROWS; col++) {
Matrix[row].b[col] = random(0xff);
}
}
}

void injectGlider() {

int col = random(MATRIX_COLS);
int row = random(MATRIX_ROWS);
Matrix[col++].b[row] |= 0b0000111;
Matrix[col++].b[row] |= 0b0000001;
Matrix[col++].b[row] |= 0b0000010;

}

void rotateLeft(unsigned long long x[]) {
unsigned long long carryIn = 0, carryOut;
for (int i=0; i<4; i++) {
carryOut = x[i] >> 63;
x[i] = x[i] << 1 | carryIn;
carryIn = carryOut;
}
x[0] |= carryOut;
}

void rotateRight(unsigned long long x[]) {
unsigned long long carryIn = 0, carryOut;
for (int i=3; i>=0; i--) {
carryOut = x[i] << 63;
x[i] = x[i] >> 1 | carryIn;
carryIn = carryOut;
}
x[3] |= carryOut;
}

void scrollLeft() {
Matrix[MATRIX_COLS] = Matrix[0];
for (int row = 0; row < MATRIX_COLS+1; row++) Matrix[row] = Matrix[row+1];
}

void scrollRight() {
for (int row = MATRIX_COLS; row > 0; row--) Matrix[row] = Matrix[row-1];
Matrix[0] = Matrix[MATRIX_COLS];
}

void scrollUp() {
for (int row = 0; row < MATRIX_COLS; row++) rotateLeft(Matrix[row].l);
}

void scrollDown() {
for (int row = 0; row < MATRIX_COLS; row++) rotateRight(Matrix[row].l);
}

int generateMatrix() {

//Variables holding data on neighbouring cells
MatrixData NeighbourN, NeighbourNW, NeighbourNE, CurrCells, NeighbourW, NeighbourE, NeighbourS, NeighbourSW, NeighbourSE;

//Variables used in calculating new cells
unsigned long long tot1, carry, tot2, tot4, NewCells;

int changes = 0; // counts the changes in the matrix
static int prevChanges[4]; // counts the changes in the matrix on prev 4 generations
static int staleCount = 0; // counts the consecutive occurrances of the same number of changes in the matrix
leftChanges = rightChanges = topChanges = bottomChanges = 0;

//set up N, NW, NE, W & E neighbour data
NeighbourN = Matrix[MATRIX_COLS-1];
CurrCells = Matrix[0];
Matrix[MATRIX_COLS] = CurrCells; // copy row 0 to location after last row to remove need for wrap-around code in the loop

NeighbourNW = NeighbourN;
rotateLeft(NeighbourNW.l);
NeighbourNE = NeighbourN;
rotateRight(NeighbourNE.l);

NeighbourW = CurrCells;
rotateLeft(NeighbourW.l);
NeighbourE = CurrCells;
rotateRight(NeighbourE.l);

//Process each row of the matrix
for (int row = 0; row < MATRIX_COLS; row++) {

//Pick up new S, SW & SE neighbours
NeighbourS = Matrix[row + 1];

NeighbourSW = NeighbourS;
rotateLeft(NeighbourSW.l);

NeighbourSE = NeighbourS;
rotateRight(NeighbourSE.l);

for (int i=0; i<MATRIX_ROWS/8; i++) {
//Count the live neighbours (in parallel) for the current row of cells
//However, if total goes over 3, we don't care (see below), so counting stops at 4
tot1 = NeighbourN.l[i];
tot2 = tot1 & NeighbourNW.l[i]; tot1 = tot1 ^ NeighbourNW.l[i];
carry = tot1 & NeighbourNE.l[i]; tot1 = tot1 ^ NeighbourNE.l[i]; tot4 = tot2 & carry; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourW.l[i]; tot1 = tot1 ^ NeighbourW.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourE.l[i]; tot1 = tot1 ^ NeighbourE.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourS.l[i]; tot1 = tot1 ^ NeighbourS.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourSW.l[i]; tot1 = tot1 ^ NeighbourSW.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourSE.l[i]; tot1 = tot1 ^ NeighbourSE.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;

//Calculate the updated cells:
// <2 or >3 neighbours, cell dies
// =2 neighbours, cell continues to live
// =3 neighbours, new cell born
NewCells = (CurrCells.l[i] | tot1) & tot2 & ~ tot4;

//Have any cells changed?
if (NewCells != CurrCells.l[i]) {
//Count the change for "stale" test
changes++;
if (row >= MATRIX_COLS/2) rightChanges++; else leftChanges++;
if (i >= MATRIX_ROWS/16) bottomChanges++; else topChanges++;
Matrix[row].l[i] = NewCells;
}
}

//Current cells (before update), E , W, SE, SW and S neighbours become
//new N, NW, NE, E, W neighbours and current cells for next loop
NeighbourN = CurrCells;
NeighbourNW = NeighbourW;
NeighbourNE = NeighbourE;
NeighbourE = NeighbourSE;
NeighbourW = NeighbourSW;
CurrCells = NeighbourS;
}

if (changes != prevChanges[0] && changes != prevChanges[1] && changes != prevChanges[2] && changes != prevChanges[3]) {
staleCount = 0;
}
else {
staleCount++; //Detect "stale" matrix
}

if (staleCount > 64) injectGlider(); //Inject a glider

for (int i=3; i>0; i--) {
prevChanges[i] = prevChanges[i-1];
}

prevChanges[0] = changes;
}


RogerClark
Sat Jul 04, 2015 9:42 pm
Paul

Looks very cool.

Did you figure out your crash bug?

Also, in your yourtube comments,
Can you link to my Youtube vids about how you install, and also to the repo and this forum?

You could try one of those coloured SPI based displays, eg ILI9341 (though it may be a bit slower unless you can use DMA to do the transfers)


PaulRB
Sun Jul 05, 2015 4:25 pm
Hi Roger, links added to Youtube video.

Don’t know if I still have the blue throbbing/crash issue, will leave the sketch running a few hours and see what happens. Maybe even small changes can trigger/avoid the issue.

Thanks for making all this happen on the Arduino IDE, by the way!


RogerClark
Sun Jul 05, 2015 9:06 pm
Hi Paul

If you still have the issue with blue throbbing, let me know and i will see if i can replicate it and then try running with a STLink adaptor and GDB running to see if it can shed any light on why its crashing


mrburnette
Sun Jul 05, 2015 10:28 pm
PaulRB wrote:And now the mini joystick is removed and the view is automatically panned around the grid to try to show a part where something interesting is going on.

Video


PaulRB
Mon Jul 06, 2015 5:57 am
Thanks Ray. Do you remember my early attempts on Picaxe? Around 10 frames per second on a 16 x16 grid…

Roger, ran the code continuously for over 3 hours yesterday with no crash/blue throbbing. No idea what I did to fix it, unfortunately. If it starts up again, will attempt to isolate what I changed that causes it, although if it only occurs after several hours, I’m likely to miss it.


RogerClark
Mon Jul 06, 2015 6:23 am
Paul

I wonder if a loose connection to the display could cause it (not quite sure how)

Anyway. There’s no way to debug it if we can’t replicate the bug

Where did I hear that before….. ;-)


mrburnette
Mon Jul 06, 2015 12:12 pm
PaulRB wrote:Thanks Ray. Do you remember my early attempts on Picaxe? Around 10 frames per second on a 16 x16 grid…

Leave a Reply

Your email address will not be published. Required fields are marked *