Игра «Жизнь» (Game of Life) — клеточный автомат, придуманный английским программистом Джоном Конвеем в 1970 году. Наверное, по популярности среди программистов игра Жизнь занимает второе место после программы «Привет Мир!».

Место действия моей игры — «чашка Петри» — это размеченный на клетки прямоугольник. По периметру прямоугольника расположены пустые клетки. Они во время игры не меняются и на экране не отображаются. Каждая клетка на этой поверхности может находиться в двух состояниях: быть «живой» 1 или быть «мёртвой» 0 – пустой.

Клетка имеет восемь соседей, окружающих её. Распределение живых клеток в начале игры называется первым поколением. Каждое следующее поколение рассчитывается на основе предыдущего по правилам. Клетка жива ровно 1 поколение. Если у клетки имеется ровно 2 соседа, в клетке появляется жизнь. Более двух клеток не приносят плодов из-за перенаселённости.

Игра заканчивается когда умирают все клетки или ситуация на поле повторяется, что не всегда бывает просто определить. 

Схема подключения LED дисплея SSD1306 I2C очень простая, поэтому её здесь не привожу. У дисплея всего 4 вывода, из них 2 питание от 3,3 до 5 Вольт. Подключите вывод SDA на плате дисплея к выводу A4, а вывод SCK к выводу A5 на плате Arduino. Будем использовать аппаратный I2C интерфейс микроконтроллера ATMega 328 со стандартной для среды Arduino IDE библиотекой Whare.

#include <Wire.h>

#define SSD1306_Address             0x3C
#define SSD1306_Command_Mode        0x80
#define SSD1306_Dats_Mode           0x40

#define SSD1306_MEMORYMODE          0x20 //2< Автоматическая адресация
#define SSD1306_COLUMNADDR          0x21 //3< See datasheet
#define SSD1306_PAGEADDR            0x22 //3< See datasheet
#define SSD1306_SETSTARTLINE        0x40 ///< See datasheet
#define SSD1306_SETCONTRAST         0x81 //2< Контрастность
#define SSD1306_CHARGEPUMP          0x8D //2< Умножитель напряжения
#define SSD1306_SEGREMAP            0xA1 ///< Развёртка слева/направо
#define SSD1306_DISPLAYALLON_RESUME 0xA4 ///< отображение содержимого RAM
#define SSD1306_NORMALDISPLAY       0xA6 ///< Нет инверсии
#define SSD1306_SETMULTIPLEX        0xA8 //2< See datasheet
#define SSD1306_DISPLAYOFF          0xAE ///< Выключить дисплей
#define SSD1306_DISPLAYON           0xAF ///< Дисплей включен
#define SSD1306_COMSCANDEC          0xC8 ///< Развёртка сверху/вниз
#define SSD1306_SETDISPLAYOFFSET    0xD3 //2< See datasheet
#define SSD1306_SETDISPLAYCLOCKDIV  0xD5 //2< Частота обновления
#define SSD1306_SETCOMPINS          0xDA //2< Разрешение 0x02-128x32, 0x12-128x64

unsigned char ledComIni1[] {
  SSD1306_SETMULTIPLEX, 0x3F,
  SSD1306_SETDISPLAYOFFSET, 0x00,
  SSD1306_SETSTARTLINE,
  SSD1306_MEMORYMODE, 0x00,
  SSD1306_COLUMNADDR, 0x00, 0x7F,
  SSD1306_PAGEADDR, 0x00, 0x07,
  SSD1306_SEGREMAP,
  SSD1306_COMSCANDEC,
  SSD1306_SETCOMPINS, 0x12,
  SSD1306_SETCONTRAST, 0x7F,
  SSD1306_NORMALDISPLAY,
  SSD1306_SETDISPLAYCLOCKDIV, 0x20,
  SSD1306_CHARGEPUMP, 0x14,
  SSD1306_DISPLAYALLON_RESUME,
  SSD1306_DISPLAYON
};

unsigned char scrBlock[8], blBlock[8];
unsigned char block1[8] {0xFC, 0xFC, 0xCC, 0xCC, 0xFC, 0xFC, 0x00, 0x00};

#define X 18  /* 18 maximum */
#define Y 10  /* 10 maximum */
bool state[X][Y], old[X][Y];

void setup() {
  Wire.begin();
  Wire.setClock(400000);
  for (int n = 0; n < sizeof(ledComIni1); n++) {
    sendCommand(ledComIni1[n]);
  }
  for (int n = 0; n < 512; n++) {
    sendDats(blBlock);
  }
  int n = (X - 2) * 8 - 1;
  ledComIni1[9] = n;
  n = Y - 3;
  ledComIni1[12] = n;
  state[10][3] = 1;
  state[10][5] = 1;
  state[6][3] = 1;
  state[5][5] = 1;
  prnt();
  delay (1000);
}

void loop() {
  delay (100);
  play();
  prnt();
}

void prnt () {
  for (int n = 0; n < sizeof(ledComIni1); n++) {
    sendCommand(ledComIni1[n]);
  }
  for (int y = 1; y < Y - 1; y++) {
    for (int x = 1; x < X - 1; x++) {
      if (state[x][y]) {
        sendDats(block1);
      }
      else {
        sendDats(blBlock);
      }
    }
  }
}

void play() {
  for (int x = 1; x < X - 1; x++) {
    for (int y = 1; y < Y - 1; y++) {
      old[x][y] = state[x][y];
      state[x][y] = 0;
    }
  }
  for (int x = 1; x < X - 1; x++) {
    for (int y = 1; y < Y - 1; y++) {
      if ((old[x][y + 1] + old[x][y - 1] + old[x + 1][y] + old[x - 1][y] + old[x + 1][y + 1] + old[x - 1][y - 1] + old[x + 1][y - 1] + old[x - 1][y + 1]) == 2) {
        state[x][y] = !old[x][y];
      }
    }
  }
}

void sendDats(unsigned char dim[8]) {
  Wire.beginTransmission(SSD1306_Address);
  Wire.write(SSD1306_Dats_Mode);
  for (int n = 0; n < 8; n++)
    Wire.write(dim[n]);
  Wire.endTransmission();
}

void sendCommand(unsigned char command) {
  Wire.beginTransmission(SSD1306_Address);
  Wire.write(SSD1306_Command_Mode);
  Wire.write(command);
  Wire.endTransmission();
}

Рис. 2. Программа для игры Жизнь.

В 1940-х годах известный программист Джон фон Нейман создал гипотетическую машину, которая может воспроизводить сама себя. Математическая модель машины фон Неймана получилась с очень сложными правилами. Конвей упростил идеи, предложенные Нейманом, и создал правила, которые стали правилами игры «Жизнь».

Играть в «Жизнь» можно на поле любого размера, на поле свёрнутом в трубочку или в тор. Могут быть различные начальные условия. Разные правила рождения. Одно правило остаётся неизменным – следующие поколения ничего не знают о предыдущих.