So I attempted to write Conway's game of life on an Arduino and display using the FastLED library. I use a custom bitset class to manage the game board state. I'm looking for feedback on performance, and general code style towards embedded systems.
I should note that my led strip is a little bit weird see diagram below to show how 4 rows work in it, and 4 columns. It kind of snakes back and forth, with zero being in the top right. My actual grid has 8 columns on it, and can be daisy chained to get more rows.
+----+----+----+----+
| 3 | 2 | 1 | 0 |
+----+----+----+----+
| 4 | 5 | 6 | 7 |
+----+----+----+----+
| 11 | 10 | 9 | 8 |
+----+----+----+----+
| 12 | 13 | 14 | 15 |
+----+----+----+----+
/**
Game of Life with LEDS and variable HUE
Assumes a square grid of leds on a 8x8 led matrix.
Controlled with WS2812B led controller.
*/
#include <FastLED.h>
/**
* How long should each frame be displayed roughly
*/
#define FRAME_TIME 500
/**
* Should we draw the red border. If so we reduce the playfield by one on each side.
* Undefine this if we should not draw it
*/
#define DRAW_BORDER
//#undef DRAW_BORDER
/**
* The width of the grid
*/
#define WIDTH 8
/**
* The height of the grid
*/
#define HEIGHT 32
/**
* The initial number of live cells in the grid. They are randomly placed.
*/
#define NUMBER_OF_INITIAL_LIVE_CELLS 16
/**
* WS2812B Data pin
*/
#define DATA_PIN 3
/*
* Computed Values based on above constants
*/
#ifdef DRAW_BORDER
// We provide a spot for the border to go.
#define GRID_X_START 1
#define GRID_X_END (WIDTH - 1)
#define GRID_Y_START 1
#define GRID_Y_END (HEIGHT - 1)
#else
#define GRID_X_START 0
#define GRID_X_END WIDTH
#define GRID_Y_START 0
#define GRID_Y_END HEIGHT
#endif // DRAW_BORDER
#define NUM_LEDS (WIDTH * HEIGHT)
/**************************************************
* Begin Main Code Below
**************************************************/
int computeBitNumber(byte x, byte y) {
return y * WIDTH + x;
}
template<size_t N>
class MyBitset {
public:
MyBitset& operator=(const MyBitset& b) {
memcpy(this->data, b.data, N/8);
}
void setBit(size_t idx, byte val) {
size_t idx2 = idx / 8;
int bit2 = idx % 8;
bitWrite(data[idx2], bit2, val);
}
void zeroArray() {
memset(data, 0, N/8);
}
byte getBit(size_t idx) const {
size_t idx2 = idx / 8;
return bitRead(data[idx2], idx % 8);
}
private:
byte data[N/8];
};
const CRGB BORDER_COLOR = CRGB(255, 25, 25);
const CRGB WAS_LIVE_COLOR = CHSV(115, 82, 60);
const CRGB LIVE_COLOR = CHSV(115, 82, 100);
const CRGB LIVE_AND_WAS_COLOR = CHSV(115, 82, 140);
CRGB leds[NUM_LEDS];
MyBitset<NUM_LEDS> current, prev;
CRGB& getLed(byte x, byte y) {
int xOffset = y & 1 ? (WIDTH - 1) - x : x;
return leds[y * WIDTH + xOffset];
}
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
FastLED.setBrightness(100);
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
// Randomize the initial grid everytime on start up
setupBorder();
generateRandomGame();
prev = current;
FastLED.show();
}
void loop() {
int startTime = millis();
setupBorder();
current.zeroArray();
for (int x = GRID_X_START; x < GRID_X_END; ++x) {
for (int y = GRID_Y_START; y < GRID_Y_END; ++y) {
int count = countNeighbors(x, y);
int index = computeBitNumber(x, y);
CRGB& targetLed = getLed(x, y);
if (count == 2 || count == 3) {
current.setBit(index, 1);
targetLed = prev.getBit(index) ? LIVE_AND_WAS_COLOR : LIVE_COLOR;
} else {
current.setBit(index, 0);
targetLed = prev.getBit(index) ? WAS_LIVE_COLOR : CRGB::Black;
}
}
}
prev = current;
int finishTime = millis();
Serial.println(finishTime - startTime);
FastLED.show();
FastLED.delay(FRAME_TIME - (finishTime - startTime));
}
int countNeighbors(byte xCenter, byte yCenter) {
int sum = 0;
for (int x = xCenter - 1; x < xCenter + 2; ++x) {
for (int y = yCenter - 1; y < yCenter + 2; ++y) {
if (x >= GRID_X_END || x < GRID_X_START || y < GRID_Y_START || y >= GRID_Y_END)
continue;
sum += prev.getBit(computeBitNumber(x,y));
}
}
return sum - prev.getBit(computeBitNumber(xCenter, yCenter));
}
/**
* Clears the LED array to black using memset.
*/
void setupBorder() {
memset(leds, 0, sizeof(leds));
#ifdef DRAW_BORDER
for (int i = 0; i < WIDTH; ++i) {
getLed(i, 0) = BORDER_COLOR;
getLed(i, GRID_Y_END) = BORDER_COLOR;
}
for (int i = GRID_Y_START; i < HEIGHT; ++i) {
getLed(0, i) = BORDER_COLOR;
getLed(GRID_X_END, i) = BORDER_COLOR;
}
#endif // DRAW_BORDER
}
void generateRandomGame() {
for (int i = 0; i < NUMBER_OF_INITIAL_LIVE_CELLS; ++i) {
int x, y, v;
do {
x = random(GRID_X_START, GRID_X_END);
y = random(GRID_Y_START, GRID_Y_END);
v = computeBitNumber(x, y);
} while(current.getBit(v) > 0);
current.setBit(v, 1);
getLed(x, y) = LIVE_COLOR;
}
}