Skip to content

Instantly share code, notes, and snippets.

@jonphilpott
Created April 13, 2022 19:46
Show Gist options
  • Save jonphilpott/e30dbd48e2bdf35c1f859df82a513bc3 to your computer and use it in GitHub Desktop.
Save jonphilpott/e30dbd48e2bdf35c1f859df82a513bc3 to your computer and use it in GitHub Desktop.
// GAME OF LIFE KOSMO MODULE
// instead of walking the field and turning them into clock pulses, generate clock pulses based on the number of dead/alive cells
//
// split area into 4 zones, any time there is change in that area, create a clock pulse.
// pot on analog input to change the refresh rate
// double buffered
#define GOL_W (96)
#define GOL_H (32)
#define N_ALIVE_CELLS (20)
#define GOL_BYTE_SIZE ( ((GOL_W) * (GOL_H)) / 8 )
#define MAX_GENS 128
#define PULSE_CYCLES 1
#define GOL_CYCLES 4
#define GOL_SPEED_INPUT A0
#define CLK_SPEED_INPUT A1
#define CLK_OUTPUT1 6
#define CLK_OUTPUT2 7
#define CLK_OUTPUT3 8
#define CLK_OUTPUT4 9
#define RATE_POT_PIN A0
byte GAME_LIFE1[(GOL_BYTE_SIZE)];
byte GAME_LIFE2[(GOL_BYTE_SIZE)];
#include <avr/wdt.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library.
// On an arduino UNO: A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO: 2(SDA), 3(SCL), ...
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
/**************************************************************************
* ACTIVITY BOX CODE
*/
#define N_ACTIVITY_BOXES (4)
struct activity_box {
unsigned int x, y, w, h;
};
struct activity_box activity_boxes[N_ACTIVITY_BOXES] = {
{2, 2, 12, 12},
{20, 18, 12, 12},
{40, 2, 12, 12},
{60, 2, 12, 12}
};
unsigned int activity_box_status[N_ACTIVITY_BOXES] = {0, 0, 0, 0};
int check_box(struct activity_box *box)
{
for (int y = box->y ; y < (box->y + box->h) ; y++) {
for (int x = box->x ; x < (box->x + box->w); x++) {
if (gol_get(x, y) == 0 && gol_get2(x, y)) {
return 1;
}
}
}
return 0;
}
void check_activity_boxes()
{
for (int i = 0 ; i < N_ACTIVITY_BOXES ; i++) {
struct activity_box *box = &activity_boxes[i];
activity_box_status[i] = check_box(box);
}
}
void draw_activity_boxes()
{
for (int i = 0 ; i < N_ACTIVITY_BOXES ; i++) {
struct activity_box *box = &activity_boxes[i];
display.drawRect(box->x, box->y, box->w, box->h, SSD1306_WHITE);
}
}
void shuffle_activity_boxes()
{
for (int i = 0; i < N_ACTIVITY_BOXES ; i++) {
struct activity_box *box = &activity_boxes[i];
box->h = 4+random(GOL_H-6);
box->w = 4+random(16);
box->y = (random(GOL_H - box->h));
}
}
void draw_activity_box_statuses()
{
for (int i = 0 ; i < N_ACTIVITY_BOXES ; i++) {
struct activity_box *box = &activity_boxes[i];
unsigned sx = 100;
unsigned sy = 2 + (i * 16);
if (activity_box_status[i]) {
display.fillRect(sx, sy, 8, 14, SSD1306_WHITE);
}
}
}
void clear_box_statuses()
{
for (int i = 0 ; i < N_ACTIVITY_BOXES ; i++) {
activity_box_status[i] = 0;
}
}
void output_pulses()
{
digitalWrite(CLK_OUTPUT1, activity_box_status[0] ? HIGH : LOW);
digitalWrite(CLK_OUTPUT2, activity_box_status[1] ? HIGH : LOW);
digitalWrite(CLK_OUTPUT3, activity_box_status[2] ? HIGH : LOW);
digitalWrite(CLK_OUTPUT4, activity_box_status[3] ? HIGH : LOW);
}
void clear_pulses()
{
digitalWrite(CLK_OUTPUT1, LOW);
digitalWrite(CLK_OUTPUT2, LOW);
digitalWrite(CLK_OUTPUT3, LOW);
digitalWrite(CLK_OUTPUT4, LOW);
}
/**************************************************************************
* GAME OF LIFE CODE
*/
int gol_get(signed int x, signed int y)
{
if (x < 0 || y < 0 || x >= GOL_W || y >= GOL_H) {
return 0;
}
int posn = (y * GOL_W) + x;
int byte_pos = posn / 8;
int bit_pos = posn % 8;
byte bit_mask = (1 << bit_pos);
return ((GAME_LIFE1[byte_pos] & bit_mask) ? 1 : 0);
}
int gol_get2(signed int x, signed int y)
{
if (x < 0 || y < 0 || x >= GOL_W || y >= GOL_H) {
return 0;
}
int posn = (y * GOL_W) + x;
int byte_pos = posn / 8;
int bit_pos = posn % 8;
byte bit_mask = (1 << bit_pos);
return ((GAME_LIFE2[byte_pos] & bit_mask) ? 1 : 0);
}
void gol_set(signed int x, signed int y, int n)
{
if (x < 0 || y < 0 || x > GOL_W || y > GOL_H) {
return;
}
int posn = (y * GOL_W) + x;
int byte_pos = posn / 8;
int bit_pos = posn % 8;
byte bit_mask = (1 << bit_pos);
if (n) {
GAME_LIFE2[byte_pos] |= bit_mask;
}
else {
bit_mask = ~bit_mask;
GAME_LIFE2[byte_pos] &= bit_mask;
}
}
void gol_buffer_swap()
{
memcpy(GAME_LIFE1, GAME_LIFE2, sizeof(GAME_LIFE1));
}
int neighbours(int x, int y)
{
int cnt = gol_get(x - 1, y - 1) + gol_get(x, y - 1) + gol_get(x + 1, y - 1) +
gol_get(x - 1, y) + 0 + gol_get(x + 1, y) +
gol_get(x - 1, y + 1) + gol_get(x, y + 1) + gol_get(x + 1, y + 1);
return cnt;
}
unsigned int gol_run()
{
unsigned char x = 0;
unsigned char y = 0;
int cnt = 0;
int cur = 0;
int nxt = 0;
memset(GAME_LIFE2, 0, sizeof(GAME_LIFE2));
unsigned int alive = 0;
for (y = 0; y < GOL_H; y++) {
for (x = 0 ; x < GOL_W ; x++) {
cnt = neighbours(x, y);
cur = gol_get(x, y);
nxt = ((cnt | cur) == 3);
gol_set(x, y, nxt);
}
}
check_activity_boxes();
gol_buffer_swap();
return alive;
}
/**************************************************************************
* GAME OF LIFE CREATURES
*/
// create creatures in buffer2, make sure you swap...
void place_glider(int x, int y)
{
gol_set(x + 1, y, 1);
gol_set(x + 2, y + 1, 1);
gol_set(x + 3, y, 1); gol_set(x + 3, y + 1, 1); gol_set(x + 3, y + 2, 1);
}
void place_spaceship(int x, int y)
{
gol_set(x + 1, y, 1); gol_set(x + 4, y, 1);
gol_set(x, y + 1, 1);
gol_set(x, y + 2, 1); gol_set(x + 4, y + 2, 1);
gol_set(x, y + 3, 1); gol_set(x + 1, y + 3, 1); gol_set(x + 2, y + 3, 1); gol_set(x + 3, y + 3, 1);
}
void place_clock(int x, int y)
{
gol_set(x + 2, y, 1);
gol_set(x, y + 1, 1); gol_set(x + 2, y + 1, 1);
gol_set(x + 1, y + 2, 1); gol_set(x + 3, y + 2, 1);
gol_set(x + 1, y + 3, 1);
}
void place_deca(int x, int y)
{
gol_set(x + 2, y, 1); gol_set(x + 7, y, 1);
y++;
gol_set(x + 0, y, 1); gol_set(x + 1, y, 1); gol_set(x + 3, y, 1); gol_set(x + 4, y, 1); gol_set(x + 5, y, 1); gol_set(x + 6, y, 1); gol_set(x + 8, y, 1); gol_set(x + 9, y, 1);
y++;
gol_set(x + 2, y, 1); gol_set(x + 7, y, 1);
y++;
}
void place_25p(int x, int y)
{
gol_set(x + 10, y, 1);
y++;
gol_set(x + 8, y, 1); gol_set(x + 9, y, 1); gol_set(x + 10, y, 1); gol_set(x + 12, y, 1); gol_set(x + 13, y, 1); gol_set(x + 14, y, 1);
y++;
gol_set(x + 7, y, 1); gol_set(x + 8, y, 1); gol_set(x + 15, y, 1);
y++;
gol_set(x + 2, y, 1); gol_set(x + 6, y, 1); gol_set(x + 9, y, 1); gol_set(x + 13, y, 1); gol_set(x + 14, y, 1);
y++;
gol_set(x + 1, y, 1); gol_set(x + 2, y, 1); gol_set(x + 3, y, 1); gol_set(x + 4, y, 1);
y++;
gol_set(x + 0, y, 1); gol_set(x + 4, y, 1);
y++;
gol_set(x + 1, y, 1); gol_set(x + 3, y, 1); gol_set(x + 6, y, 1);
y++;
gol_set(x + 5, y, 1);
y++;
}
void place_gg(int x, int y)
{
gol_set(x + 24, y, 1);
y++;
gol_set(x + 22, y, 1); gol_set(x + 24, y, 1);
y++;
gol_set(x + 12, y, 1); gol_set(x + 13, y, 1); gol_set(x + 20, y, 1); gol_set(x + 21, y, 1); gol_set(x + 34, y, 1); gol_set(x + 35, y, 1);
y++;
gol_set(x + 11, y, 1); gol_set(x + 15, y, 1); gol_set(x + 20, y, 1); gol_set(x + 21, y, 1); gol_set(x + 34, y, 1); gol_set(x + 35, y, 1);
y++;
gol_set(x + 0, y, 1); gol_set(x + 1, y, 1); gol_set(x + 10, y, 1); gol_set(x + 16, y, 1); gol_set(x + 20, y, 1); gol_set(x + 21, y, 1);
y++;
gol_set(x + 0, y, 1); gol_set(x + 1, y, 1); gol_set(x + 10, y, 1); gol_set(x + 14, y, 1); gol_set(x + 16, y, 1); gol_set(x + 17, y, 1); gol_set(x + 22, y, 1); gol_set(x + 24, y, 1);
y++;
gol_set(x + 10, y, 1); gol_set(x + 16, y, 1); gol_set(x + 24, y, 1);
y++;
gol_set(x + 11, y, 1); gol_set(x + 15, y, 1);
y++;
gol_set(x + 12, y, 1); gol_set(x + 13, y, 1);
y++;
}
void place_loafer(int x, int y)
{
gol_set(x + 1, y, 1); gol_set(x + 2, y, 1); gol_set(x + 5, y, 1); gol_set(x + 7, y, 1); gol_set(x + 8, y, 1);
y++;
gol_set(x + 0, y, 1); gol_set(x + 3, y, 1); gol_set(x + 6, y, 1); gol_set(x + 7, y, 1);
y++;
gol_set(x + 1, y, 1); gol_set(x + 3, y, 1);
y++;
gol_set(x + 2, y, 1);
y++;
gol_set(x + 8, y, 1);
y++;
gol_set(x + 6, y, 1); gol_set(x + 7, y, 1); gol_set(x + 8, y, 1);
y++;
gol_set(x + 5, y, 1);
y++;
gol_set(x + 6, y, 1);
y++;
gol_set(x + 7, y, 1); gol_set(x + 8, y, 1);
y++;
}
void place_cluster(int x, int y)
{
for (int i = 0 ; i < 16 ; i++) {
gol_set(x+random(8), y+random(8), 1);
}
}
void gol_reset()
{
memset(GAME_LIFE2, 0, sizeof(GAME_LIFE2));
int n_creatures = 2 + random(10);
for (int i = 0; i < n_creatures ; i++) {
int sx = i * (GOL_W / n_creatures);
int sy = random(GOL_H / 3) * 4;
switch (random(12)) {
case 0:
case 1:
case 2:
case 3: place_glider(sx, sy); break;
case 4: place_spaceship(sx, sy); break;
case 5: place_clock(sx, sy); break;
case 6: place_deca(sx, sy); break;
case 7: place_25p(sx, sy); break;
case 8: place_gg(sx, sy); break;
case 9: place_loafer(sx, sy); break;
default: place_cluster(sx, sy); break;
}
}
gol_buffer_swap();
}
/**************************************************************************
* ARDUINO SETUP AND MAIN LOOPS
*/
void setup() {
randomSeed(analogRead(0));
Serial.begin(9600);
pinMode(CLK_OUTPUT1, OUTPUT);
pinMode(CLK_OUTPUT2, OUTPUT);
pinMode(CLK_OUTPUT3, OUTPUT);
pinMode(CLK_OUTPUT4, OUTPUT);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Don't proceed, loop forever
}
memset(GAME_LIFE1, 0x0, sizeof(GAME_LIFE1));
memset(GAME_LIFE2, 0x0, sizeof(GAME_LIFE2));
// Show initial display buffer contents on the screen --
// the library initializes this with an Adafruit splash screen.
display.clearDisplay();
display.display();
gol_reset();
// enable watchdog
wdt_enable(WDTO_500MS);
}
int byte_pointer = 0;
int bit_pointer = 0;
int generations = MAX_GENS;
unsigned int gol_counter = GOL_CYCLES;
unsigned int gol_incr = 255;
unsigned int pulse_counter = 0;
void loop() {
wdt_reset();
// render screen output
display.clearDisplay();
display.drawBitmap(0, 0, GAME_LIFE1, GOL_W, GOL_H, SSD1306_WHITE);
unsigned current_y = bit_pointer / GOL_W;
unsigned current_x = bit_pointer % GOL_W;
display.drawPixel(current_x, current_y, SSD1306_WHITE);
draw_activity_boxes();
if (pulse_counter == 0) {
clear_pulses();
}
else {
draw_activity_box_statuses();
}
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
display.setTextSize(3);
display.setCursor(0, 32);
display.println(F("life"));
display.drawRect(96, 0, 3, SCREEN_HEIGHT, SSD1306_WHITE);
display.drawLine(97, 0, 97, (int) map(generations, 0, MAX_GENS, 0, SCREEN_HEIGHT), SSD1306_WHITE);
display.display();
// run GOL if we're going to overflow
if (gol_counter == 0) {
gol_run();
generations--;
pulse_counter = PULSE_CYCLES;
output_pulses();
gol_counter = GOL_CYCLES;
}
if (generations == 0) {
gol_reset();
generations = MAX_GENS;
shuffle_activity_boxes();
}
int potValue = analogRead(RATE_POT_PIN);
int dly = map(potValue, 0, 1023, 0, 1000);
delay(dly);
pulse_counter--;
gol_counter--;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment